Line # Revision Author
1 197 ahitrov package session::AUTH::FaceBook;
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 Contenido::Globals;
12
13 use vars qw($VERSION);
14 $VERSION = '4.1';
15
16 =for rem
17 facebook:
18 auto_create_user: 1
19 239 ahitrov app_id: 15 decimal digits
20 app_secret: 32 hex digits
21 authorize_url: https://www.facebook.com/dialog/oauth
22 197 ahitrov access_token_url: https://graph.facebook.com/oauth/access_token
23 user_info_url: https://graph.facebook.com/me
24 user_post_url: ~
25 239 ahitrov state: is passed back to your app as a parameter of the redirect_uri when the user completed the authentication
26 197 ahitrov store:
27 class: "+Comments::Authentication::Store"
28 type: facebook
29
30 =cut
31
32 our $JSON = JSON::XS->new->utf8;
33
34 =for rem SCHEMA
35
36 242 ahitrov $m->redirect ( $fb_connect->authorize_url( facebook_redirect_uri => ... )->as_string );
37 197 ahitrov
38
39 =cut
40
41
42 sub new {
43 my ($class, %config) = @_;
44 my $self = bless {}, $class;
45 240 ahitrov
46 $self->{facebook_authorize_url} = 'https://www.facebook.com/dialog/oauth';
47 $self->{facebook_access_token_url} = 'https://graph.facebook.com/oauth/access_token';
48 $self->{facebook_user_info_url} = 'https://graph.facebook.com/me';
49
50 for (qw(facebook_app_id facebook_app_secret)) {
51 239 ahitrov $self->{$_} = $config{$_} || $state->{session}{$_} || return undef;
52 197 ahitrov }
53 239 ahitrov $self->{timeout} = $state->{session}{connection_timeout} || 3;
54 320 ahitrov for (qw(facebook_user_post_url facebook_redirect_uri facebook_scope)) {
55 239 ahitrov $self->{$_} = $config{$_} || $state->{session}{$_};
56 197 ahitrov }
57 return $self;
58 }
59
60 242 ahitrov sub authorize_url {
61 197 ahitrov my $self = shift;
62 my (%args) = @_;
63 my $go = URI->new( $self->{facebook_authorize_url} );
64 239 ahitrov $go->query_param( client_id => $self->{facebook_app_id} );
65 $go->query_param( state => $args{state} ) if $args{state};
66 320 ahitrov $go->query_param( scope => $self->{facebook_scope} ) if $self->{facebook_scope};
67 197 ahitrov $args{redirect_uri} ||= $self->{facebook_redirect_uri};
68 for ( keys %args ) {
69 $go->query_param( $_ => $args{$_} );
70 }
71 239 ahitrov warn Dumper($go) if $DEBUG;
72 197 ahitrov return $go;
73 }
74
75 sub authenticate {
76 my ( $self, %authinfo ) = @_;
77 warn "FB.authenticate" if $DEBUG;
78 # TODO: we need callback url
79 my $local_session = $session || $keeper->{session}->get_session;
80 239 ahitrov my $redirect_uri = $self->{facebook_redirect_uri};
81 197 ahitrov
82 my $access_token = $local_session->{facebook_access_token};
83 my $expires = $local_session->{facebook_expires};
84 if ($access_token and $expires > time) {
85 warn "Already have access_token" if $DEBUG;
86 } else {
87 undef $access_token;
88 }
89 my $code = $authinfo{'code'};
90 unless ( $code ) {
91 warn "Call to authenticate without code";
92 return undef;
93 }
94 my $ua = LWP::UserAgent->new;
95 $ua->timeout($self->{timeout});
96 unless ($access_token) {
97 my $req = URI->new( $self->{facebook_access_token_url});
98 $req->query_param( client_id => $self->{facebook_app_id} );
99 $req->query_param( redirect_uri => $redirect_uri );
100 $req->query_param( client_secret=> $self->{facebook_app_secret} );
101 $req->query_param( code => $code);
102 239 ahitrov warn "Get $req" if $DEBUG;
103 197 ahitrov my $res = $ua->get($req);
104 unless ($res->code == 200) {
105 warn "access_token request failed: ".$res->status_line;
106 return undef;
107 }
108 my %res = eval { URI->new("?".$res->content)->query_form };
109 239 ahitrov warn Dumper(\%res) if $DEBUG;
110 197 ahitrov unless ($access_token = $res{access_token}) {
111 warn "No access token in response: ".$res->content;
112 return undef;
113 }
114 272 ahitrov $local_session->set( facebook_access_token => $access_token );
115 197 ahitrov if( my $expires = $res{expires} ) {
116 272 ahitrov $local_session->set( facebook_expires => time + $expires );
117 197 ahitrov } else {
118 272 ahitrov #$local_session->set( facebook_expires => time + 3600*24 );
119 197 ahitrov }
120 239 ahitrov warn "FB: requested access token" if $DEBUG;
121 197 ahitrov } else {
122 239 ahitrov warn "FB: have access token" if $DEBUG;
123 197 ahitrov }
124
125 my $req = URI->new( $self->{facebook_user_info_url} );
126 $req->query_param( access_token => $access_token );
127
128 239 ahitrov warn "Fetching user $req" if $DEBUG;
129 197 ahitrov my $res = $ua->get($req);
130 unless ($res->code == 200) {
131 warn "user request failed: ".$res->status_line;
132 return undef;
133 }
134 my $info;
135 unless ( $info = eval { JSON::XS->new->utf8->decode($res->content) } ) {
136 warn "user '".$res->content."' decode failed: $@";
137 return undef;
138 }
139 239 ahitrov warn "Userhash = ".Dumper($info) if $DEBUG;
140 197 ahitrov #warn "facebook: user=$info->{name} / $info->{id} / $info->{gender}";
141
142 272 ahitrov $local_session->delete( 'facebook_redirect_url' );
143 197 ahitrov
144 my @plugins = split (/[\ |\t]+/, $state->{plugins});
145 245 ahitrov my $name = Encode::encode('utf-8', $info->{name});
146 320 ahitrov my $email = exists $info->{email} && $info->{email} ? $info->{email} : undef;
147 197 ahitrov if ( grep { $_ eq 'users' } @plugins ) {
148 307 ahitrov my $user;
149 if ( $state->{users}->use_credentials ) {
150 320 ahitrov if ( $local_session->id ) {
151 $user = $keeper->{users}->get_profile( id => $local_session->{id} );
152 } else {
153 $user = $keeper->{users}->get_profile( facebook => $info->{id} );
154 }
155 309 ahitrov }
156 320 ahitrov if ( !ref $user && $email ) {
157 $user = $keeper->{users}->get_profile( email => $keeper->{users}->_email_reduction( $email ) );
158 }
159 309 ahitrov unless ( ref $user ) {
160 307 ahitrov $user = $keeper->{users}->get_profile( login => 'facebook:'.$info->{id} );
161 }
162 197 ahitrov unless ( ref $user ) {
163 my $user_class = $state->{users}->profile_document_class;
164 $user = $user_class->new( $keeper );
165 309 ahitrov my %props = map { $_->{attr} => $_ } $user->structure;
166 320 ahitrov $user->login( $email || 'facebook:'.$info->{id} );
167 197 ahitrov $user->name( $name );
168 $user->status( 1 );
169 $user->type( 0 );
170 $user->login_method('facebook');
171 309 ahitrov if ( exists $props{country} ) {
172 $user->country( $info->{locale} );
173 }
174 320 ahitrov $user->email( $email );
175 239 ahitrov
176 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
177 if ( ref $prop_ava ) {
178 my $avatar = $user->_store_image( 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large', attr => 'avatar' );
179 $user->avatar( $user->_serialize($avatar) );
180 }
181
182 197 ahitrov $user->store;
183 239 ahitrov } else {
184 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
185 if ( ref $prop_ava ) {
186 my $avatar = $user->get_image( 'avatar' );
187 unless ( ref $avatar && exists $avatar->{filename} ) {
188 my $avatar = $user->_store_image( 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large', attr => 'avatar' );
189 $user->avatar( $user->_serialize($avatar) );
190 $user->store;
191 }
192 }
193 197 ahitrov }
194 309 ahitrov if ( $state->{users}->use_credentials ) {
195 $user->create_credential(
196 facebook => $info->{id},
197 avatar => 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large',
198 );
199 320 ahitrov if ( exists $info->{email} && $info->{email} ) {
200 $user->create_credential(
201 email => $info->{email},
202 status => 1,
203 );
204 }
205 309 ahitrov }
206 409 ahitrov my %data = session::Keeper::_get_hash_from_profile( $user );
207 410 ahitrov $data{auth_by} = 'facebook';
208 409 ahitrov $data{avatar} ||= 'https://graph.facebook.com/'.$info->{username}.'/picture';
209 $data{email} ||= $email if $email;
210 272 ahitrov $local_session->set( %data );
211 242 ahitrov } else {
212 my %data = (
213 id => $info->{id},
214 name => $name,
215 login => 'facebook:'.$info->{id},
216 status => 1,
217 type => 0,
218 auth_by => 'facebook',
219 ltime => time,
220 avatar => 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large',
221 );
222 320 ahitrov $data{email} = $email if $email;
223 272 ahitrov $local_session->set ( %data );
224 197 ahitrov }
225 return $local_session;
226 }
227
228 1;