package session::AUTH::Mailru; use strict; use warnings; use LWP::UserAgent; use JSON::XS; use Data::Dumper; use URI; use URI::QueryParam; use Encode; use Digest::MD5 qw/ md5_hex /; use Contenido::Globals; use vars qw($VERSION); $VERSION = '4.1'; =for rem facebook: auto_create_user: 1 app_id: 15 decimal digits app_secret: 32 hex digits authorize_url: https://connect.mail.ru/oauth/authorize access_token_url: https://connect.mail.ru/oauth/token user_info_url: https://graph.facebook.com/me user_post_url: ~ state: is passed back to your app as a parameter of the redirect_uri when the user completed the authentication store: class: "+Comments::Authentication::Store" type: facebook =cut our $JSON = JSON::XS->new->utf8; =for rem SCHEMA $m->redirect ( $fb_connect->authorize_url( mailru_redirect_uri => ... )->as_string ); =cut sub new { my ($class, %config) = @_; my $self = bless {}, $class; $self->{mailru_authorize_url} = 'https://connect.mail.ru/oauth/authorize'; $self->{mailru_access_token_url} = 'https://connect.mail.ru/oauth/token'; $self->{mailru_user_info_url} = 'http://www.appsmail.ru/platform/api'; for (qw(mailru_app_id mailru_app_secret)) { $self->{$_} = $config{$_} || $state->{session}{$_} || return undef; } $self->{timeout} = $state->{session}{connection_timeout} || 3; for (qw(mailru_user_post_url mailru_redirect_uri)) { $self->{$_} = $config{$_} || $state->{session}{$_}; } return $self; } sub authorize_url { my $self = shift; my (%args) = @_; my $go = URI->new( $self->{mailru_authorize_url} ); $go->query_param( client_id => $self->{mailru_app_id} ); $go->query_param( response_type => "code" ); $args{redirect_uri} ||= $self->{mailru_redirect_uri}; for ( keys %args ) { $go->query_param( $_ => $args{$_} ); } warn Dumper($go) if $DEBUG; return $go; } sub authenticate { my ( $self, %authinfo ) = @_; warn "Mailru.authenticate" if $DEBUG; my $local_session = $session || $keeper->{session}->get_session; my $redirect_uri = $self->{mailru_redirect_uri}; my $access_token = $local_session->{mailru_access_token} || $local_session->{mailru_refresh_token}; my $expires = $local_session->{mailru_expires}; if ($access_token and $expires > time) { warn "Already have access_token" if $DEBUG; } else { undef $access_token; } my $code = $authinfo{'code'}; unless ( $code ) { warn "Call to authenticate without code"; return undef; } my $ua = LWP::UserAgent->new; $ua->timeout($self->{timeout}); unless ($access_token) { my $req = URI->new( $self->{mailru_access_token_url}); $req->query_param( client_id => $self->{mailru_app_id} ); $req->query_param( grant_type => 'authorization_code' ); $req->query_param( redirect_uri => $redirect_uri ); $req->query_param( client_secret=> $self->{mailru_app_secret} ); $req->query_param( code => $code); warn "Post $req" if $DEBUG; my $res = $ua->post($req); unless ($res->code == 200) { warn "access_token request failed: ".$res->status_line; return undef; } my $info = $JSON->decode($res->content); unless ( ref $info eq 'HASH' && ($access_token = $info->{access_token}) ) { warn "No access token in response: ".$res->content."\n"; return undef; } $keeper->{session}->store_value( mailru_access_token => $access_token, mailru_refresh_token => $info->{refresh_token}, mailru_id => $info->{x_mailru_vid}, ); $local_session->{mailru_access_token} = $access_token; $local_session->{mailru_refresh_token} = $info->{refresh_token}; $local_session->{mailru_id} = $info->{x_mailru_vid}; if( my $expires = $info->{expires_in} ) { $local_session->{mailru_expires} = time + $expires; $keeper->{session}->store_value( mailru_expires => $local_session->{mailru_expires} ); } else { #$c->user_session->{'expires'} = time + 3600*24; } warn "Mailru: requested access token" if $DEBUG; } else { warn "Mailru: have access token" if $DEBUG; } my $req = URI->new( $self->{mailru_user_info_url} ); my %params = ( session_key => $access_token, app_id => $self->{mailru_app_id}, method => 'users.getInfo', uids => $local_session->{mailru_id}, secure => 1, ); while ( my ($param, $value) = each %params ) { $req->query_param( $param => $value ); } warn "SIG String: [".join ( '', map { $_.'='.$params{$_} } sort keys %params) . $self->{mailru_app_secret}."]\n"; my $sig = md5_hex( join ( '', map { $_.'='.$params{$_} } sort keys %params) . $self->{mailru_app_secret} ); $req->query_param( sig => $sig ); warn "Fetching user $req" if $DEBUG; my $res = $ua->get($req); unless ($res->code == 200) { warn "user request failed: ".$res->status_line; return undef; } my $info; unless ( $info = eval { $JSON->decode($res->content) } ) { warn "user '".$res->content."' decode failed: $@"; return undef; } if ( ref $info eq 'ARRAY' && @$info ) { $info = $info->[0]; } else { warn "Mailru: non-natural user info responce ".Dumper($info)."\n"; return undef; } foreach my $key ( qw(nick last_name first_name) ) { $info->{$key} = Encode::encode('utf-8', $info->{$key}); Encode::from_to( $info->{$key}, 'utf-8', 'koi8-r' ); } warn "Userhash = ".Dumper($info) if $DEBUG; #warn "facebook: user=$info->{name} / $info->{id} / $info->{gender}"; $keeper->{session}->delete_key( 'mailru_redirect_url' ); delete $local_session->{mailru_redirect_url}; my @plugins = split (/[\ |\t]+/, $state->{plugins}); my $name = $info->{first_name}.' '.$info->{last_name}; if ( grep { $_ eq 'users' } @plugins ) { my $user = $keeper->{users}->get_profile( email => $info->{email} ) if $info->{email}; $user ||= $keeper->{users}->get_profile( login => 'mailru:'.$info->{uid} ); unless ( ref $user ) { my $user_class = $state->{users}->profile_document_class; $user = $user_class->new( $keeper ); $user->login( $info->{email} || 'mailru:'.$info->{uid} ); $user->name( $name ); $user->nickname( $info->{nick} ); $user->status( 1 ); $user->type( 0 ); $user->login_method('mailru'); if ( $info->{location} ) { my $country = Encode::encode('utf-8', $info->{location}{country}{name}); Encode::from_to( $country, 'utf-8', 'koi8-r' ); $user->country( $country ); } if ( $info->{birthday} && $info->{birthday} =~ /(\d{2})\.(\d{2})\.(\d{4})/ ) { $user->dtime( "$3-$2-$1" ); } $user->email( $info->{email} || undef ); my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure; if ( ref $prop_ava && $info->{pic_big} ) { my $avatar = $user->_store_image( $info->{pic_big}, attr => 'avatar' ); $user->avatar( $user->_serialize($avatar) ); } $user->store; } else { my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure; if ( ref $prop_ava ) { my $avatar = $user->get_image( 'avatar' ); if ( $info->{pic_big} && !(ref $avatar && exists $avatar->{filename}) ) { my $avatar = $user->_store_image( $info->{pic_big}, attr => 'avatar' ); $user->avatar( $user->_serialize($avatar) ); $user->store; } } } my %data = ( id => $user->id, name => $user->name, nick => $user->nickname, login => $user->login, email => $user->email, status => $user->status, type => $user->type, ltime => time, avatar => $info->{pic}, ); $keeper->{session}->store_value ( %data ); while ( my ( $key, $value ) = each %data ) { $local_session->{$key} = $value; } } else { my %data = ( id => $info->{uid}, name => $name, nick => $info->{nick} || $name, email => $info->{email}, login => $info->{email} || 'mailru:'.$info->{uid}, status => 1, type => 0, auth_by => 'mailru', ltime => time, ); if ( $user->{pic} ) { $data{avatar} = $info->{pic}; } $keeper->{session}->store_value ( %data ); while ( my ( $key, $value ) = each %data ) { $local_session->{$key} = $value; } } return $local_session; } 1;