package session::AUTH::VKontakte; use strict; use warnings; use LWP::UserAgent; use IO::Socket::SSL; use JSON::XS; use Data::Dumper; use URI; use URI::QueryParam; use Encode; use Contenido::Globals; use vars qw($VERSION); $VERSION = '4.1'; =for rem vkontakte: auto_create_user: 1 app_id: decimal digits app_secret: 32 hex digits authorize_url: http://oauth.vk.com/authorize access_token_url: https://oauth.vk.com/access_token user_info_url: https://api.vk.com/method/account.getProfileInfo user_post_url: ~ =cut our $JSON = JSON::XS->new->utf8; =for rem SCHEMA $m->redirect ( $vk_connect->authorize_url( vk_redirect_uri => ... )->as_string ); =cut our %SCOPE = ( 'notify' => 1, 'friends' => 2, 'photos' => 4, 'audio' => 8, 'video' => 16, 'docs' => 131072, 'notes' => 2048, 'pages' => 128, 'menu_link' => 256, 'status' => 1024, 'groups' => 262144, 'email' => 4194304, 'notifications' => 524288, 'stats' => 1048576, 'ads' => 32768, 'offline' => 65536, ); sub new { my ($class, %config) = @_; my $self = bless {}, $class; $self->{vk_authorize_url} = 'https://oauth.vk.com/authorize'; $self->{vk_access_token_url} = 'https://oauth.vk.com/access_token'; $self->{vk_user_info_url} = 'https://api.vk.com/method/getProfiles'; for (qw( vk_app_id vk_app_secret )) { $self->{$_} = $config{$_} || $state->{session}->{$_} || return undef; } if ( $config{vk_scope} || $state->{session}->{vk_scope} ) { $self->{vk_scope} = $config{vk_scope} || $state->{session}->{vk_scope}; } $self->{timeout} = $state->{session}->{connection_timeout} || 3; for (qw(vk_user_post_url vk_redirect_uri)) { $self->{$_} = $config{$_} || $state->{session}->{$_}; } $self->{vk_api_version} = '5.52'; return $self; } sub authorize_url { my $self = shift; my (%args) = @_; my $go = URI->new( $self->{vk_authorize_url} ); $go->query_param( client_id => $self->{vk_app_id} ); $go->query_param( scope => $self->{vk_scope} || '' ); $go->query_param( display => 'page' ); $go->query_param( response_type => 'code' ); $go->query_param( v => $self->{vk_api_version} ); $args{redirect_uri} ||= $self->{vk_redirect_uri}; for ( keys %args ) { $go->query_param( $_ => $args{$_} ); } warn "VK AUTH URL: $go\n" if $DEBUG; my $local_session = $session || $keeper->{session}->get_session; $local_session->set( vk_redirect_url => $self->{vk_redirect_uri} ); return $go; } sub authenticate { my ( $self, %authinfo ) = @_; warn "VK.authenticate" if $DEBUG; my $local_session = $session || $keeper->{session}->get_session; my $redirect_uri = $self->{vk_redirect_uri}; my $access_token = $local_session->{vk_access_token}; my $vk_user_id = $local_session->{vk_user_id}; my $expires = $local_session->{vk_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\n"; return undef; } my $ua = LWP::UserAgent->new; $ua->timeout($self->{timeout}); unless ($access_token) { my $req = URI->new( $self->{vk_access_token_url}); $req->query_param( client_id => $self->{vk_app_id} ); $req->query_param( client_secret => $self->{vk_app_secret} ); $req->query_param( code => $code ); $req->query_param( redirect_uri => $redirect_uri ); $req->query_param( v => $self->{vk_api_version} ); warn "Token request: [$req]\n" if $DEBUG; my $res = $ua->get($req); unless ($res->code == 200) { warn "VK: Access_token request failed: ".$res->status_line."\n"; return undef; } my $info = $JSON->decode($res->content); warn Dumper $info if $DEBUG; unless ( ref $info eq 'HASH' && ($access_token = $info->{access_token}) ) { warn "No access token in response: ".$res->content."\n"; return undef; } $local_session->set( vk_access_token => $access_token ); $local_session->set( vk_user_id => $info->{user_id} ); if ( exists $info->{email} ) { $local_session->set( vk_email => $info->{email} ); $local_session->set( email => $info->{email} ) unless exists $local_session->{email}; } if ( my $expires = $info->{expires_in} ) { $local_session->set( vk_expires => time + $expires ); } else { #$local_session->set( vk_expires => time + 3600*24 ); } warn "VK: requested access token" if $DEBUG; } else { warn "VK: have access token" if $DEBUG; } my @fields = qw( uid first_name last_name nickname domain sex bdate city country timezone photo photo_medium photo_big ); my $req = URI->new( $self->{vk_user_info_url} ); $req->query_param( uid => $local_session->{vk_user_id} ); $req->query_param( fields => join ',', @fields ); $req->query_param( access_token => $access_token ); $req->query_param( v => $self->{vk_api_version} ); warn "VK: Fetching user $req\n" if $DEBUG; my $res = $ua->get($req); unless ($res->code == 200) { warn "VK: user request failed: ".$res->status_line."\n"; return undef; } my $info; unless ( $info = eval { $JSON->decode($res->content) } ) { warn "user '".$res->content."' decode failed: $@\n"; return undef; } warn Dumper($info) if $DEBUG; return undef unless exists $info->{response} && ref $info->{response} eq 'ARRAY' && @{$info->{response}}; my $user_info = $info->{response}[0]; foreach my $key ( qw(nickname last_name first_name) ) { $user_info->{$key} = Encode::encode('utf-8', $user_info->{$key}); } my @plugins = split (/[\ |\t]+/, $state->{plugins}); my $name = $user_info->{first_name}.' '.$user_info->{last_name}; my $email = exists $user_info->{email} && $user_info->{email} ? $user_info->{email} : undef; if ( grep { $_ eq 'users' } @plugins ) { my $user; if ( $state->{users}->use_credentials ) { if ( $local_session->id ) { $user = $keeper->{users}->get_profile( id => $local_session->{id} ); } else { $user = $keeper->{users}->get_profile( vkontakte => $user_info->{uid} ); } } if ( !ref $user && (exists $local_session->{vk_email} || exists $local_session->{email}) ) { $user = $keeper->{users}->get_profile( email => exists $local_session->{vk_email} ? $local_session->{vk_email} : $local_session->{email} ); } unless ( ref $user ) { $user = $keeper->{users}->get_profile( login => 'vkontakte:'.$user_info->{uid} ); } unless ( ref $user ) { my $user_class = $state->{users}->profile_document_class; $user = $user_class->new( $keeper ); my %props = map { $_->{attr} => $_ } $user->structure; $user->login( exists $local_session->{vk_email} ? $local_session->{vk_email} : 'vkontakte:'.$user_info->{uid} ); $user->name( $user_info->{last_name}.', '.$user_info->{first_name} ); $user->nickname( $user_info->{nickname} ); $user->status( 1 ); $user->type( 0 ); $user->login_method('vkontakte'); if ( exists $props{country} ) { $user->country( $user_info->{country} ); } $user->email( $local_session->{vk_email} ? $local_session->{vk_email} : undef ); my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure; if ( ref $prop_ava ) { my $avatar = $user->_store_image( $user_info->{photo_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' ); unless ( ref $avatar && exists $avatar->{filename} ) { my $avatar = $user->_store_image( $user_info->{photo_big}, attr => 'avatar' ); $user->avatar( $user->_serialize($avatar) ); $user->store; } } } if ( $state->{users}->use_credentials ) { $user->create_credential( name => $user_info->{last_name}.', '.$user_info->{first_name}, vkontakte => $user_info->{uid}, avatar => $user_info->{photo_big}, ); } my %data = session::Keeper::_get_hash_from_profile( $user ); $data{auth_by} = 'vkontakte'; if ( $user_info->{photo} ) { $data{avatar} ||= $user_info->{photo}; } $local_session->set( %data ); } else { my %data = ( id => $user_info->{uid}, name => $name, nick => $user_info->{nickname} || $name, login => exists $local_session->{vk_email} ? $local_session->{vk_email} : 'vkontakte:'.$user_info->{uid}, status => 1, type => 0, auth_by => 'vkontakte', ltime => time, ); if ( $user_info->{photo} ) { $data{avatar} = $user_info->{photo}; } $local_session->set( %data ); } return $local_session; } 1;