Line # Revision Author
1 246 ahitrov package session::AUTH::Google;
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: string
21 app_secret: 32 hex digits
22 authorize_url: https://accounts.google.com/o/oauth2/auth
23 access_token_url: https://accounts.google.com/o/oauth2/token
24 user_info_url: https://www.googleapis.com/oauth2/v1/userinfo
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 ( $g_connect->authorize_url( 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->{google_authorize_url} = 'https://accounts.google.com/o/oauth2/auth';
48 $self->{google_access_token_url} = 'https://accounts.google.com/o/oauth2/token';
49 $self->{google_user_info_url} = 'https://www.googleapis.com/oauth2/v2/userinfo';
50 # $self->{scope} = 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email';
51 $self->{scope} = $config{scope} || $state->{session}{google_scope} || 'https://www.googleapis.com/auth/userinfo.profile';
52
53 for (qw(google_app_id google_app_secret)) {
54 $self->{$_} = $config{$_} || $state->{session}{$_} || return undef;
55 }
56 $self->{timeout} = $state->{session}{connection_timeout} || 3;
57 for (qw(google_user_post_url google_redirect_uri)) {
58 $self->{$_} = $config{$_} || $state->{session}{$_};
59 }
60 return $self;
61 }
62
63 sub authorize_url {
64 my $self = shift;
65 my (%args) = @_;
66 my $go = URI->new( $self->{google_authorize_url} );
67 $go->query_param( client_id => $self->{google_app_id} );
68 # $go->query_param( state => $args{state} ) if $args{state};
69 $go->query_param( response_type => "code" );
70 $go->query_param( scope => $self->{scope} );
71 $args{redirect_uri} ||= $self->{google_redirect_uri};
72 for ( keys %args ) {
73 $go->query_param( $_ => $args{$_} );
74 }
75 warn Dumper($go) if $DEBUG;
76 return $go;
77 }
78
79 sub authenticate {
80 my ( $self, %authinfo ) = @_;
81 warn "Google.authenticate" if $DEBUG;
82
83 my $local_session = $session || $keeper->{session}->get_session;
84 my $redirect_uri = $self->{google_redirect_uri};
85
86 my $access_token = $local_session->{google_access_token} || $local_session->{google_refresh_token};
87 my $expires = $local_session->{google_expires};
88 if ($access_token and $expires > time) {
89 warn "Already have access_token" if $DEBUG;
90 } else {
91 undef $access_token;
92 }
93 my $code = $authinfo{'code'};
94 unless ( $code ) {
95 warn "Call to authenticate without code";
96 return undef;
97 }
98 my $ua = LWP::UserAgent->new;
99 $ua->timeout($self->{timeout});
100 unless ($access_token) {
101 my $req = URI->new( $self->{google_access_token_url});
102 my %post_params = (
103 code => $code,
104 client_id => $self->{google_app_id},
105 client_secret => $self->{google_app_secret},
106 redirect_uri => $redirect_uri,
107 grant_type => 'authorization_code',
108 );
109 warn "Post: $req".Dumper(\%post_params) if $DEBUG;
110 my $res = $ua->post($req, \%post_params);
111 unless ($res->code == 200) {
112 warn "access_token request failed: ".$res->status_line;
113 return undef;
114 }
115 my $info = $JSON->decode($res->content);
116 unless ( ref $info eq 'HASH' && ($access_token = $info->{access_token}) ) {
117 warn "No access token in response: ".$res->content."\n";
118 return undef;
119 }
120 $keeper->{session}->store_value(
121 google_access_token => $access_token,
122 google_refresh_token => $info->{refresh_token},
123 );
124 $local_session->{google_access_token} = $access_token;
125 $local_session->{google_refresh_token} = $info->{refresh_token} if $info->{refresh_token};
126 if( my $expires = $info->{expires_in} ) {
127 $local_session->{google_expires} = time + $expires;
128 $keeper->{session}->store_value( google_expires => $local_session->{google_expires} );
129 } else {
130 #$c->user_session->{'expires'} = time + 3600*24;
131 }
132 warn "Google: requested access token: $access_token" if $DEBUG;
133 } else {
134 warn "Google: have access token" if $DEBUG;
135 }
136
137 my $req = URI->new( $self->{google_user_info_url} );
138 $req->query_param( access_token => $access_token );
139 $ua->credentials("googleapis.com:80", "Authorization", "Bearer", $access_token);
140
141 warn "Fetching user $req" if $DEBUG;
142 my $res = $ua->get($req);
143 unless ($res->code == 200) {
144 warn "user request failed: ".$res->status_line;
145 return undef;
146 }
147 my $info;
148 unless ( $info = eval { $JSON->decode($res->content) } ) {
149 warn "user '".$res->content."' decode failed: $@";
150 return undef;
151 }
152
153 foreach my $key ( qw(name family_name given_name) ) {
154 $info->{$key} = Encode::encode('utf-8', $info->{$key});
155 }
156 warn "Userhash = ".Dumper($info) if $DEBUG;
157
158 my @plugins = split (/[\ |\t]+/, $state->{plugins});
159 my $name = $info->{name};
160 if ( grep { $_ eq 'users' } @plugins ) {
161 my $user = $keeper->{users}->get_profile( email => $info->{email} ) if $info->{email};
162 $user ||= $keeper->{users}->get_profile( login => 'google:'.$info->{id} );
163 unless ( ref $user ) {
164 my $user_class = $state->{users}->profile_document_class;
165 $user = $user_class->new( $keeper );
166 $user->login( $info->{email} || 'google:'.$info->{id} );
167 $user->name( $name );
168 $user->status( 1 );
169 $user->type( 0 );
170 $user->login_method('google');
171 if ( $info->{locale} ) {
172 $user->country( $info->{locale} );
173 }
174 if ( $info->{birthday} && $info->{birthday} =~ /(\d{2})\.(\d{2})\.(\d{4})/ ) {
175 $user->dtime( "$3-$2-$1" );
176 }
177 $user->email( $info->{email} || undef );
178
179 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
180 if ( ref $prop_ava && $info->{picture} ) {
181 my $avatar = $user->_store_image( $info->{picture}, attr => 'avatar' );
182 $user->avatar( $user->_serialize($avatar) );
183 }
184
185 $user->store;
186 } else {
187 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
188 if ( ref $prop_ava ) {
189 my $avatar = $user->get_image( 'avatar' );
190 if ( $info->{picture} && !(ref $avatar && exists $avatar->{filename}) ) {
191 my $avatar = $user->_store_image( $info->{picture}, attr => 'avatar' );
192 $user->avatar( $user->_serialize($avatar) );
193 $user->store;
194 }
195 }
196 }
197 my %data = (
198 id => $user->id,
199 name => $user->name,
200 login => $user->login,
201 email => $user->email,
202 status => $user->status,
203 type => $user->type,
204 ltime => time,
205 avatar => $info->{picture},
206 );
207 $keeper->{session}->store_value ( %data );
208 while ( my ( $key, $value ) = each %data ) {
209 $local_session->{$key} = $value;
210 }
211 } else {
212 my %data = (
213 id => $info->{id},
214 name => $name,
215 gender => $info->{gender} ? ($info->{gender} eq 'male' ? 'm' : $info->{gender} eq 'female' ? 'f' : undef) : undef,
216 email => $info->{email},
217 login => $info->{email} || 'google:'.$info->{id},
218 status => 1,
219 type => 0,
220 auth_by => 'google',
221 ltime => time,
222 );
223 if ( $user->{picture} ) {
224 $data{avatar} = $info->{picture};
225 }
226 $keeper->{session}->store_value ( %data );
227 while ( my ( $key, $value ) = each %data ) {
228 $local_session->{$key} = $value;
229 }
230 }
231 return $local_session;
232 }
233
234 1;