darsan_auth.pm 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #!/usr/bin/perl
  2. use Modern::Perl;
  3. # Авторизация для Дарсана
  4. # Ю. Жиловец, 17.02.2015, 12.05.2023
  5. package darsan_auth;
  6. use Crypt::OpenSSL::RSA;
  7. use Mojo::JSON qw/j/;
  8. use Mojo::UserAgent;
  9. use Convert::Ascii85 qw/ascii85_encode/;
  10. use Time::timegm qw/timegm/;
  11. use POSIX::strptime;
  12. use Data::Dumper;
  13. use Mojo::Promise;
  14. sub error
  15. {
  16. return $_[0]->{error};
  17. }
  18. sub source_ip
  19. {
  20. my $self = shift;
  21. my $ip = shift;
  22. $self->{ua}->socket_options({LocalAddr => $ip});
  23. }
  24. sub as_server
  25. {
  26. my $class = shift;
  27. my $auth_servers = shift;
  28. my $name = shift;
  29. my $private = shift;
  30. if (ref($private) ne "Crypt::OpenSSL::RSA")
  31. {
  32. open(K,$private) or do die "No private key found: $private: $!";
  33. my $key = do { local($/); <K>};
  34. close(K);
  35. $private = Crypt::OpenSSL::RSA->new_private_key($key);
  36. $private->use_sha256_hash;
  37. $private->use_pkcs1_oaep_padding;
  38. }
  39. my $auth_server = ref $auth_servers ? $auth_servers->[0] : $auth_servers;
  40. return bless {auth_server=>$auth_server, name=>$name, private=>$private, error=>undef, ua=>new Mojo::UserAgent},$class;
  41. }
  42. sub as_user
  43. {
  44. my $class = shift;
  45. my $auth_servers = shift;
  46. my $login = shift;
  47. my $password = shift;
  48. my $auth_server = ref $auth_servers ? $auth_servers->[0] : $auth_servers;
  49. return bless {auth_server=>$auth_server, login=>$login, password=>$password, error=>undef, ua=>new Mojo::UserAgent},$class;
  50. }
  51. sub set_token
  52. {
  53. my $self = shift;
  54. my $token = shift;
  55. my $exp_stamp = shift;
  56. $self->{token} = $token;
  57. $self->{expire} = $exp_stamp;
  58. }
  59. sub token
  60. {
  61. my $self = shift;
  62. my $sub = shift;
  63. undef $self->{error};
  64. return $self->{token} if $self->{token} && $self->{expire}>time();
  65. my ($err,$token,$expire) = $self->_fetch_token_sync;
  66. if ($err)
  67. {
  68. $self->{error} = $err;
  69. return undef;
  70. }
  71. $self->{token} = $token;
  72. $self->{expire} = $expire;
  73. return $token;
  74. }
  75. sub token_p
  76. {
  77. my $self = shift;
  78. return Mojo::Promise->resolve($self->{token}) if $self->{token} && $self->{expire}>time();
  79. $self->_fetch_token_async->then(sub
  80. {
  81. my ($token, $expire) = @_;
  82. $self->{token} = $token;
  83. $self->{expire} = $expire;
  84. return $token;
  85. });
  86. }
  87. sub _token_query
  88. {
  89. my $self = shift;
  90. if ($self->{login})
  91. {
  92. return ( "$self->{auth_server}/token", {ver=>2, login=>$self->{login}, password=>$self->{password}} );
  93. }
  94. else
  95. {
  96. my $t = time();
  97. my $sign = $self->{private}->sign("$self->{name}/$t");
  98. return ( "$self->{auth_server}/server-token/$self->{name}", {ver=>2, time=>$t, sign=>ascii85_encode($sign)} );
  99. }
  100. }
  101. sub _fetch_token_sync
  102. {
  103. my $self = shift;
  104. my ($url, $form) = $self->_token_query;
  105. my $tx = $self->{ua}->post($url => form => $form);
  106. return $self->_parse_reply($tx);
  107. }
  108. sub _fetch_token_async
  109. {
  110. my $self = shift;
  111. my $sub = shift;
  112. my ($url, $form) = $self->_token_query;
  113. $self->{ua}->post_p($url => form => $form)
  114. ->catch(sub
  115. {
  116. my $err = shift;
  117. die {url=>$url, code=>500, message=>$err, body=>""};
  118. })
  119. ->then(sub
  120. {
  121. my $tx = shift;
  122. my ($error, $token, $expires) = $self->_parse_reply($tx);
  123. die $error if $error;
  124. return ($token, $expires);
  125. })
  126. }
  127. sub _parse_reply
  128. {
  129. my $self = shift;
  130. my $tx = shift;
  131. my $body = $tx->res->body;
  132. $body = j($body) if $tx->res->headers->content_type && $tx->res->headers->content_type =~ m|^application/json|;
  133. if (my $e = $tx->error)
  134. {
  135. $e->{code} ||= 500;
  136. my $err = {url=>$tx->req->url."", code=>$e->{code}, message=>$e->{message}, body=>$tx->res->body};
  137. return ($err);
  138. }
  139. my $token = $body->{token};
  140. my $expires = $body->{expires};
  141. my $exp = parse_time($expires);
  142. return (undef, $token, $exp);
  143. }
  144. sub parse_time {
  145. no warnings;
  146. my $s = shift;
  147. my $time_format = "%Y-%m-%d %H:%M:%S%z";
  148. $s =~ /([+-])(\d{4})$/;
  149. my $tz_sign = $1;
  150. my $tz = $2;
  151. $s =~ s/[+-]\d{4}$//;
  152. my $time = timegm(POSIX::strptime($s, $time_format));
  153. if ($tz) {
  154. my $h = substr($tz, 0, 2);
  155. my $s = substr($tz, 2, 2);
  156. my $offset = ($h * 60 + $s) * 60;
  157. $offset = -$offset if $tz_sign eq "+";
  158. $time += $offset;
  159. }
  160. return $time;
  161. }
  162. 1;