Revision 844

Date:
2022/11/25 09:45:08
Author:
ahitrov
Revision Log:
Yandex oAuth

Files:

Legend:

 
Added
 
Removed
 
Modified
  • utf8/plugins/session/comps/www/oauth/yandex.html

     
    1 <!DOCTYPE html>
    2 <head>
    3 <script type="text/javascript">
    4 <!--
    5 opener.location.reload(true);
    6 close();
    7 //-->
    8 </script>
    9 </head>
    10 </html>
    11 <%doc>
    12
    13 use LWP::UserAgent;
    14 use JSON::XS;
    15 use URI;
    16 use Encode;
    17 use URI;
    18 use URI::QueryParam;
    19 my $JSON = JSON::XS->new->utf8;
    20
    21 Manual redirect:
    22 use session::AUTH::VKontakte;
    23 my $site = $state->development ? 'www' : 'www';
    24 my $vk_connect = session::AUTH::VKontakte->new(
    25 vk_redirect_uri => 'http://'.$site.'/oauth/vkontakte.html',
    26 );
    27
    28
    29 </%doc>
    30 <%once>
    31
    32 my $site = $state->development ? '' : '';
    33
    34 </%once>
    35 <%args>
    36
    37 $code => undef
    38
    39 </%args>
    40 <%init>
    41
    42 my $res;
    43 my $info;
    44
    45 my $vk_connect = session::AUTH::VKontakte->new;
    46 my $auth_url = $vk_connect->authorize_url;
    47 if ( $code ) {
    48 my $local_session = $vk_connect->authenticate( code => $code );
    49 if ( ref $local_session eq 'session::Session' && $local_session->id ) {
    50 my $profile = $keeper->{users}->get_profile( id => $local_session->id ) if exists $keeper->{users};
    51 if ( ref $profile ) {
    52 unless ( exists $local_session->{avatar} ) {
    53 my $avatar = $profile->get_image('avatar');
    54 $session->{avatar} = ref $avatar && exists $avatar->{filename} ? $avatar->{mini}{'54x54'}{filename} : undef;
    55 $local_session->set (
    56 name => $profile->name_full,
    57 last_name => $profile->name_family,
    58 first_name => $profile->name_part,
    59 avatar => $session->{avatar},
    60 );
    61 } else {
    62 $local_session->set (
    63 name => $profile->name_full,
    64 last_name => $profile->name_family,
    65 first_name => $profile->name_part,
    66 );
    67 }
    68 }
    69 }
    70 } elsif ( $auth_url ) {
    71 $m->redirect($auth_url->as_string);
    72 } else {
    73 &abort404 unless $DEBUG;
    74 }
    75
    76 </%init>
  • utf8/plugins/session/lib/session/AUTH/Yandex.pm

     
    1 package session::AUTH::Yandex;
    2
    3 use strict;
    4 use warnings;
    5 use LWP::UserAgent;
    6 use IO::Socket::SSL;
    7 use JSON::XS;
    8 use Data::Dumper;
    9 use URI;
    10 use URI::QueryParam;
    11 use HTTP::Request;
    12 use Encode;
    13 use Contenido::Globals;
    14
    15 use vars qw($VERSION);
    16 $VERSION = '4.1';
    17
    18 =for rem
    19 yandex:
    20 auto_create_user: 1
    21 app_id: decimal digits
    22 app_secret: 32 hex digits
    23 authorize_url: https://oauth.yandex.ru/authorize
    24 access_token_url: https://oauth.yandex.ru/token
    25 user_info_url: GET https://login.yandex.ru/info?
    26 [ format=json | xml | jwt]
    27 [& jwt_secret=<Cекретный ключ>]
    28 [& with_openid_identity=1 | yes | true]
    29 Authorization: OAuth <OAuth-токен>
    30 =cut
    31
    32 our $JSON = JSON::XS->new->utf8;
    33
    34 =for rem SCHEMA
    35
    36 $m->redirect ( $yandex_connect->authorize_url( yandex_redirect_uri => ... )->as_string );
    37
    38
    39 =cut
    40
    41 our %SCOPE = (
    42 'notify' => 1,
    43 'friends' => 2,
    44 'photos' => 4,
    45 'audio' => 8,
    46 'video' => 16,
    47 'docs' => 131072,
    48 'notes' => 2048,
    49 'pages' => 128,
    50 'menu_link' => 256,
    51 'status' => 1024,
    52 'groups' => 262144,
    53 'email' => 4194304,
    54 'notifications' => 524288,
    55 'stats' => 1048576,
    56 'ads' => 32768,
    57 'offline' => 65536,
    58 );
    59
    60 sub new {
    61 my ($class, %config) = @_;
    62 my $self = bless {}, $class;
    63
    64 $self->{yandex_authorize_url} = 'https://oauth.yandex.ru/authorize';
    65 $self->{yandex_access_token_url} = 'https://oauth.yandex.ru/token';
    66 $self->{yandex_user_info_url} = 'https://login.yandex.ru/info';
    67
    68 for (qw( yandex_app_id yandex_app_secret )) {
    69 $self->{$_} = $config{$_} || $state->{session}->{$_} || return undef;
    70 }
    71 if ( exists $config{yandex_scope} && ref $config{yandex_scope} eq 'ARRAY' && scalar @{$config{yandex_scope}} ) {
    72 $self->{yandex_scope} = join(' ', @{$config{yandex_scope}});
    73 } elsif ( scalar(@{$state->{session}{yandex_scope}}) ) {
    74 $self->{yandex_scope} = join(' ', @{$state->{session}->{yandex_scope}});
    75 }
    76 $self->{timeout} = $state->{session}->{connection_timeout} || 3;
    77 for (qw(yandex_redirect_uri)) {
    78 $self->{$_} = $config{$_} || $state->{session}->{$_};
    79 }
    80 return $self;
    81 }
    82
    83 sub authorize_url {
    84 my $self = shift;
    85 my (%args) = @_;
    86 my $go = URI->new( $self->{yandex_authorize_url} );
    87 $go->query_param( response_type => 'code' );
    88 $go->query_param( client_id => $self->{yandex_app_id} );
    89 $go->query_param( scope => $self->{yandex_scope} || '' );
    90 $go->query_param( display => 'page' );
    91 $go->query_param( v => $self->{yandex_api_version} );
    92 $args{redirect_uri} ||= $self->{yandex_redirect_uri};
    93 for ( keys %args ) {
    94 $go->query_param( $_ => $args{$_} );
    95 }
    96 warn "YANDEX AUTH URL: $go\n" if $DEBUG;
    97 my $local_session = $session || $keeper->{session}->get_session;
    98 $local_session->set( yandex_redirect_url => $self->{yandex_redirect_uri} );
    99 return $go;
    100 }
    101
    102 sub authenticate {
    103 my ( $self, %authinfo ) = @_;
    104 warn "YANDEX.authenticate" if $DEBUG;
    105
    106 my $local_session = $session || $keeper->{session}->get_session;
    107 my $redirect_uri = $self->{yandex_redirect_uri};
    108
    109 my $access_token = $local_session->{yandex_access_token};
    110 my $yandex_user_id = $local_session->{yandex_user_id};
    111 my $expires = $local_session->{yandex_expires};
    112 if ($access_token and $expires > time) {
    113 warn "Already have access_token" if $DEBUG;
    114 } else {
    115 undef $access_token;
    116 }
    117 my $code = $authinfo{'code'};
    118 unless ( $code ) {
    119 warn "Call to authenticate without code\n";
    120 return undef;
    121 }
    122 my $ua = LWP::UserAgent->new;
    123 $ua->timeout($self->{timeout});
    124
    125 unless ($access_token) {
    126 my $content = 'grant_type=authorization_code&code='.$code
    127 .'&client_id='.$self->{yandex_app_id}
    128 .'&client_secret='.$self->{yandex_app_secret};
    129 my $req = HTTP::Request->new(
    130 'POST',
    131 $self->{yandex_access_token_url},
    132 [
    133 'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8',
    134 'Content-Length' => length( $content ),
    135 ],
    136 $content,
    137 );
    138 warn "Token request: [".Dumper($req)."]\n" if $DEBUG;
    139 my $res = $ua->request($req);
    140 unless ($res->code == 200) {
    141 warn "YANDEX: Access_token request failed: ".$res->status_line."\n";
    142 return undef;
    143 }
    144 my $info = $JSON->decode($res->decoded_content);
    145 warn Dumper $info if $DEBUG;
    146 unless ( ref $info eq 'HASH' && ($access_token = $info->{access_token}) ) {
    147 warn "No access token in response: ".$res->decoded_content."\n";
    148 return undef;
    149 }
    150 $local_session->set( yandex_access_token => $access_token );
    151 if ( my $expires = $info->{expires_in} ) {
    152 $local_session->set( yandex_expires => time + $expires );
    153 }
    154 warn "YANDEX: requested access token" if $DEBUG;
    155 } else {
    156 warn "YANDEX: have access token" if $DEBUG;
    157 }
    158
    159
    160 $ua->default_header( 'Authorization' => "OAuth ".$access_token );
    161 my $req = URI->new( $self->{yandex_user_info_url} );
    162 $req->query_param( format => 'json' );
    163
    164 warn "YANDEX: Fetching user $req\n" if $DEBUG;
    165 my $res = $ua->get($req);
    166 unless ($res->code == 200) {
    167 warn "YANDEX: user request failed: ".$res->status_line."\n";
    168 return undef;
    169 }
    170
    171 my $user_info;
    172 unless ( $user_info = eval { $JSON->decode($res->decoded_content) } ) {
    173 warn "user '".$res->decoded_content."' decode failed: $@\n";
    174 return undef;
    175 }
    176 warn Dumper($user_info) if $DEBUG;
    177 return undef unless exists $user_info->{client_id};
    178
    179 foreach my $key ( qw(display_name last_name first_name real_name) ) {
    180 $user_info->{$key} = Encode::encode('utf-8', $user_info->{$key});
    181 }
    182
    183 my @plugins = split (/[\ |\t]+/, $state->{plugins});
    184 my $name = $user_info->{first_name}.' '.$user_info->{last_name};
    185 my $email = exists $user_info->{default_email} && $user_info->{default_email} ? $user_info->{default_email} : undef;
    186 if ( grep { $_ eq 'users' } @plugins ) {
    187 my $user;
    188 if ( $state->{users}->use_credentials ) {
    189 if ( $local_session->id ) {
    190 $user = $keeper->{users}->get_profile( id => $local_session->{id} );
    191 } else {
    192 $user = $keeper->{users}->get_profile( yandex => $user_info->{psuid} );
    193 }
    194 }
    195 if ( !ref $user && ($email || exists $local_session->{email}) ) {
    196 $user = $keeper->{users}->get_profile( email => $email || $local_session->{email} );
    197 }
    198 unless ( ref $user ) {
    199 $user = $keeper->{users}->get_profile( login => 'yandex:'.$user_info->{login} );
    200 }
    201 unless ( ref $user ) {
    202 my $user_class = $state->{users}->profile_document_class;
    203 $user = $user_class->new( $keeper );
    204 my %props = map { $_->{attr} => $_ } $user->structure;
    205 $user->login( $email || 'yandex:'.$user_info->{login} );
    206 $user->name( $user_info->{last_name}.', '.$user_info->{first_name} );
    207 $user->status( 1 );
    208 $user->type( 0 );
    209 $user->login_method('yandex');
    210 $user->email( $email );
    211
    212 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
    213 if ( ref $prop_ava && !$user_info->{is_avatar_empty} ) {
    214 my $avatar = $user->_store_image( get_avatar_url($user_info->{default_avatar_id}), attr => 'avatar' );
    215 $user->avatar( $user->_serialize($avatar) );
    216 }
    217
    218 $user->store;
    219 } else {
    220 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
    221 if ( ref $prop_ava ) {
    222 my $avatar = $user->get_image( 'avatar' );
    223 if ( !(ref $avatar && exists $avatar->{filename}) && !$user_info->{is_avatar_empty} ) {
    224 my $avatar = $user->_store_image( get_avatar_url($user_info->{default_avatar_id}), attr => 'avatar' );
    225 $user->avatar( $user->_serialize($avatar) );
    226 $user->store;
    227 }
    228 }
    229 }
    230 if ( $state->{users}->use_credentials ) {
    231 $user->create_credential(
    232 name => $user_info->{last_name}.', '.$user_info->{first_name},
    233 yandex => $user_info->{psuid},
    234 avatar => get_avatar_url($user_info->{default_avatar_id}),
    235 );
    236 }
    237 my %data = session::Keeper::_get_hash_from_profile( $user );
    238 $data{auth_by} = 'yandex';
    239 if ( !$user_info->{is_avatar_empty} ) {
    240 $data{avatar} ||= get_avatar_url($user_info->{default_avatar_id});
    241 }
    242 $local_session->set( %data );
    243
    244 } else {
    245 my %data = (
    246 id => $user_info->{psuid},
    247 name => $name,
    248 login => $email || 'yandex:'.$user_info->{login},
    249 status => 1,
    250 type => 0,
    251 auth_by => 'yandex',
    252 ltime => time,
    253 );
    254 if ( !$user_info->{is_avatar_empty} ) {
    255 $data{avatar} = get_avatar_url($user_info->{default_avatar_id});
    256 }
    257 $local_session->set( %data );
    258 }
    259 return $local_session;
    260 }
    261
    262 sub get_avatar_url {
    263 return undef unless @_ && @_[0];
    264 return 'https://avatars.mds.yandex.net/get-yapic/'.@_[0].'/islands-200';
    265 }
    266
    267 1;
  • utf8/plugins/session/lib/session/Init.pm

     
    8 8 use session::Session;
    9 9 use session::AUTH::FaceBook;
    10 10 use session::AUTH::VKontakte;
    11 use session::AUTH::Yandex;
    11 12 use session::AUTH::Mailru;
    12 13 use session::AUTH::Google;
    13 14
  • utf8/plugins/session/lib/session/State.pm.proto

     
    75 75 $self->{vk_user_post_url} = '@VK_USER_POST_URL@';
    76 76 $self->{vk_scope} = '@VK_SCOPE@';
    77 77
    78 $self->{yandex_app_id} = '@YANDEX_APP_ID@';
    79 $self->{yandex_app_secret} = '@YANDEX_APP_SECRET@';
    80 $self->{yandex_redirect_uri} = '@YANDEX_REDIRECT_URL@';
    81 $self->{yandex_scope} = [qw(@YANDEX_SCOPE@)];
    82
    78 83 $self->{mailru_app_id} = '@MAILRU_APP_ID@';
    79 84 $self->{mailru_app_secret} = '@MAILRU_APP_SECRET@';
    80 85 $self->{mailru_redirect_uri} = '@MAILRU_REDIRECT_URL@';

Небольшая справка по веткам

cnddist – контейнер, в котором хранятся все дистрибутивы всех библиотек и программных пакетов, которые использовались при построении различных версий Contenido. Если какой-то библиотеки в данном хранилище нет, инсталлятор сделает попытку "подтянуть" ее с веба (например, с CPAN). Если библиотека слишком старая, есть очень большая вероятность, что ее там уже нет. Поэтому мы храним весь хлам от всех сборок. Если какой-то дистрибутив вдруг отсутствует в cnddist - напишите нам, мы положим его туда.

koi8 – отмирающая ветка, чей код, выдача и все внутренние библиотеки заточены на кодировку KOI8-R. Вносятся только те дополнения, которые касаются внешнего вида и функционала админки, баги ядра, обязательные обновления портов и мелочи, которые легко скопипастить. В дальнейшем планируется полная остановка поддержки по данной ветке.

utf8 – актуальная ветка, заточенная под UTF-8.

Внутри каждой ветки: core – исходники ядра; install – скрипт установки инсталляции; plugins – плагины; samples – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.