Line # Revision Author
1 243 ahitrov package session::AUTH::Mailru;
2
3 use strict;
4 use warnings;
5 use LWP::UserAgent;
6 use JSON::XS;
7 use Data::Dumper;
8 use URI;
9 use URI::QueryParam;
10 use Encode;
11 use Digest::MD5 qw/ md5_hex /;
12 use Contenido::Globals;
13
14 use vars qw($VERSION);
15 $VERSION = '4.1';
16
17 =for rem
18 facebook:
19 auto_create_user: 1
20 app_id: 15 decimal digits
21 app_secret: 32 hex digits
22 authorize_url: https://connect.mail.ru/oauth/authorize
23 access_token_url: https://connect.mail.ru/oauth/token
24 user_info_url: https://graph.facebook.com/me
25 user_post_url: ~
26 state: is passed back to your app as a parameter of the redirect_uri when the user completed the authentication
27 store:
28 class: "+Comments::Authentication::Store"
29 type: facebook
30
31 =cut
32
33 our $JSON = JSON::XS->new->utf8;
34
35 =for rem SCHEMA
36
37 $m->redirect ( $fb_connect->authorize_url( mailru_redirect_uri => ... )->as_string );
38
39
40 =cut
41
42
43 sub new {
44 my ($class, %config) = @_;
45 my $self = bless {}, $class;
46
47 $self->{mailru_authorize_url} = 'https://connect.mail.ru/oauth/authorize';
48 $self->{mailru_access_token_url} = 'https://connect.mail.ru/oauth/token';
49 $self->{mailru_user_info_url} = 'http://www.appsmail.ru/platform/api';
50
51 for (qw(mailru_app_id mailru_app_secret)) {
52 $self->{$_} = $config{$_} || $state->{session}{$_} || return undef;
53 }
54 $self->{timeout} = $state->{session}{connection_timeout} || 3;
55 for (qw(mailru_user_post_url mailru_redirect_uri)) {
56 $self->{$_} = $config{$_} || $state->{session}{$_};
57 }
58 return $self;
59 }
60
61 sub authorize_url {
62 my $self = shift;
63 my (%args) = @_;
64 my $go = URI->new( $self->{mailru_authorize_url} );
65 $go->query_param( client_id => $self->{mailru_app_id} );
66 $go->query_param( response_type => "code" );
67 $args{redirect_uri} ||= $self->{mailru_redirect_uri};
68 for ( keys %args ) {
69 $go->query_param( $_ => $args{$_} );
70 }
71 warn Dumper($go) if $DEBUG;
72 return $go;
73 }
74
75 sub authenticate {
76 my ( $self, %authinfo ) = @_;
77 warn "Mailru.authenticate" if $DEBUG;
78
79 my $local_session = $session || $keeper->{session}->get_session;
80 my $redirect_uri = $self->{mailru_redirect_uri};
81
82 my $access_token = $local_session->{mailru_access_token} || $local_session->{mailru_refresh_token};
83 my $expires = $local_session->{mailru_expires};
84 if ($access_token and $expires > time) {
85 warn "Already have access_token" if $DEBUG;
86 } else {
87 undef $access_token;
88 }
89 my $code = $authinfo{'code'};
90 unless ( $code ) {
91 warn "Call to authenticate without code";
92 return undef;
93 }
94 my $ua = LWP::UserAgent->new;
95 $ua->timeout($self->{timeout});
96 unless ($access_token) {
97 my $req = URI->new( $self->{mailru_access_token_url});
98 $req->query_param( client_id => $self->{mailru_app_id} );
99 $req->query_param( grant_type => 'authorization_code' );
100 $req->query_param( redirect_uri => $redirect_uri );
101 $req->query_param( client_secret=> $self->{mailru_app_secret} );
102 $req->query_param( code => $code);
103 warn "Post $req" if $DEBUG;
104 my $res = $ua->post($req);
105 unless ($res->code == 200) {
106 warn "access_token request failed: ".$res->status_line;
107 return undef;
108 }
109 my $info = $JSON->decode($res->content);
110 unless ( ref $info eq 'HASH' && ($access_token = $info->{access_token}) ) {
111 warn "No access token in response: ".$res->content."\n";
112 return undef;
113 }
114 $keeper->{session}->store_value(
115 mailru_access_token => $access_token,
116 mailru_refresh_token => $info->{refresh_token},
117 mailru_id => $info->{x_mailru_vid},
118 );
119 $local_session->{mailru_access_token} = $access_token;
120 $local_session->{mailru_refresh_token} = $info->{refresh_token};
121 $local_session->{mailru_id} = $info->{x_mailru_vid};
122 if( my $expires = $info->{expires_in} ) {
123 $local_session->{mailru_expires} = time + $expires;
124 $keeper->{session}->store_value( mailru_expires => $local_session->{mailru_expires} );
125 } else {
126 #$c->user_session->{'expires'} = time + 3600*24;
127 }
128 warn "Mailru: requested access token" if $DEBUG;
129 } else {
130 warn "Mailru: have access token" if $DEBUG;
131 }
132
133 my $req = URI->new( $self->{mailru_user_info_url} );
134 my %params = (
135 session_key => $access_token,
136 app_id => $self->{mailru_app_id},
137 method => 'users.getInfo',
138 uids => $local_session->{mailru_id},
139 secure => 1,
140 );
141 while ( my ($param, $value) = each %params ) {
142 $req->query_param( $param => $value );
143 }
144 warn "SIG String: [".join ( '', map { $_.'='.$params{$_} } sort keys %params) . $self->{mailru_app_secret}."]\n";
145 my $sig = md5_hex( join ( '', map { $_.'='.$params{$_} } sort keys %params) . $self->{mailru_app_secret} );
146 $req->query_param( sig => $sig );
147
148 warn "Fetching user $req" if $DEBUG;
149 my $res = $ua->get($req);
150 unless ($res->code == 200) {
151 warn "user request failed: ".$res->status_line;
152 return undef;
153 }
154 my $info;
155 unless ( $info = eval { $JSON->decode($res->content) } ) {
156 warn "user '".$res->content."' decode failed: $@";
157 return undef;
158 }
159 if ( ref $info eq 'ARRAY' && @$info ) {
160 $info = $info->[0];
161 } else {
162 warn "Mailru: non-natural user info responce ".Dumper($info)."\n";
163 return undef;
164 }
165 foreach my $key ( qw(nick last_name first_name) ) {
166 $info->{$key} = Encode::encode('utf-8', $info->{$key});
167 Encode::from_to( $info->{$key}, 'utf-8', 'koi8-r' );
168 }
169 warn "Userhash = ".Dumper($info) if $DEBUG;
170 #warn "facebook: user=$info->{name} / $info->{id} / $info->{gender}";
171
172 $keeper->{session}->delete_key( 'mailru_redirect_url' );
173 delete $local_session->{mailru_redirect_url};
174
175 my @plugins = split (/[\ |\t]+/, $state->{plugins});
176 my $name = $info->{first_name}.' '.$info->{last_name};
177 if ( grep { $_ eq 'users' } @plugins ) {
178 my $user = $keeper->{users}->get_profile( email => $info->{email} ) if $info->{email};
179 $user ||= $keeper->{users}->get_profile( login => 'mailru:'.$info->{uid} );
180 unless ( ref $user ) {
181 my $user_class = $state->{users}->profile_document_class;
182 $user = $user_class->new( $keeper );
183 $user->login( $info->{email} || 'mailru:'.$info->{uid} );
184 $user->name( $name );
185 $user->nickname( $info->{nick} );
186 $user->status( 1 );
187 $user->type( 0 );
188 $user->login_method('mailru');
189 if ( $info->{location} ) {
190 my $country = Encode::encode('utf-8', $info->{location}{country}{name});
191 Encode::from_to( $country, 'utf-8', 'koi8-r' );
192 $user->country( $country );
193 }
194 if ( $info->{birthday} && $info->{birthday} =~ /(\d{2})\.(\d{2})\.(\d{4})/ ) {
195 $user->dtime( "$3-$2-$1" );
196 }
197 $user->email( $info->{email} || undef );
198
199 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
200 if ( ref $prop_ava && $info->{pic_big} ) {
201 my $avatar = $user->_store_image( $info->{pic_big}, attr => 'avatar' );
202 $user->avatar( $user->_serialize($avatar) );
203 }
204
205 $user->store;
206 } else {
207 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
208 if ( ref $prop_ava ) {
209 my $avatar = $user->get_image( 'avatar' );
210 if ( $info->{pic_big} && !(ref $avatar && exists $avatar->{filename}) ) {
211 my $avatar = $user->_store_image( $info->{pic_big}, attr => 'avatar' );
212 $user->avatar( $user->_serialize($avatar) );
213 $user->store;
214 }
215 }
216 }
217 my %data = (
218 id => $user->id,
219 name => $user->name,
220 nick => $user->nickname,
221 login => $user->login,
222 email => $user->email,
223 status => $user->status,
224 type => $user->type,
225 ltime => time,
226 avatar => $info->{pic},
227 );
228 $keeper->{session}->store_value ( %data );
229 while ( my ( $key, $value ) = each %data ) {
230 $local_session->{$key} = $value;
231 }
232 } else {
233 my %data = (
234 id => $info->{uid},
235 name => $name,
236 nick => $info->{nick} || $name,
237 email => $info->{email},
238 login => $info->{email} || 'mailru:'.$info->{uid},
239 status => 1,
240 type => 0,
241 auth_by => 'mailru',
242 ltime => time,
243 );
244 if ( $user->{pic} ) {
245 $data{avatar} = $info->{pic};
246 }
247 $keeper->{session}->store_value ( %data );
248 while ( my ( $key, $value ) = each %data ) {
249 $local_session->{$key} = $value;
250 }
251 }
252 return $local_session;
253 }
254
255 1;