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 – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.