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 197 ahitrov for (qw(facebook_user_post_url facebook_redirect_uri)) {
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 197 ahitrov $go->query_param( scope => "publish_stream" );
67 $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 197 ahitrov if ( grep { $_ eq 'users' } @plugins ) {
147 307 ahitrov my $user;
148 if ( $state->{users}->use_credentials ) {
149 $user = $keeper->{users}->get_profile( facebook => $info->{id} );
150 309 ahitrov }
151 unless ( ref $user ) {
152 307 ahitrov $user = $keeper->{users}->get_profile( login => 'facebook:'.$info->{id} );
153 }
154 197 ahitrov unless ( ref $user ) {
155 my $user_class = $state->{users}->profile_document_class;
156 $user = $user_class->new( $keeper );
157 309 ahitrov my %props = map { $_->{attr} => $_ } $user->structure;
158 197 ahitrov $user->login( 'facebook:'.$info->{id} );
159 $user->name( $name );
160 $user->status( 1 );
161 $user->type( 0 );
162 $user->login_method('facebook');
163 309 ahitrov if ( exists $props{country} ) {
164 $user->country( $info->{locale} );
165 }
166 197 ahitrov $user->email( undef );
167 239 ahitrov
168 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
169 if ( ref $prop_ava ) {
170 my $avatar = $user->_store_image( 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large', attr => 'avatar' );
171 $user->avatar( $user->_serialize($avatar) );
172 }
173
174 197 ahitrov $user->store;
175 239 ahitrov } else {
176 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
177 if ( ref $prop_ava ) {
178 my $avatar = $user->get_image( 'avatar' );
179 unless ( ref $avatar && exists $avatar->{filename} ) {
180 my $avatar = $user->_store_image( 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large', attr => 'avatar' );
181 $user->avatar( $user->_serialize($avatar) );
182 $user->store;
183 }
184 }
185 197 ahitrov }
186 309 ahitrov if ( $state->{users}->use_credentials ) {
187 $user->create_credential(
188 facebook => $info->{id},
189 avatar => 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large',
190 );
191 }
192 197 ahitrov my %data = (
193 id => $user->id,
194 name => $user->name,
195 login => $user->login,
196 status => $user->status,
197 type => $user->type,
198 ltime => time,
199 239 ahitrov avatar => 'https://graph.facebook.com/'.$info->{username}.'/picture',
200 197 ahitrov );
201 272 ahitrov $local_session->set( %data );
202 242 ahitrov } else {
203 my %data = (
204 id => $info->{id},
205 name => $name,
206 login => 'facebook:'.$info->{id},
207 status => 1,
208 type => 0,
209 auth_by => 'facebook',
210 ltime => time,
211 avatar => 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large',
212 );
213 272 ahitrov $local_session->set ( %data );
214 197 ahitrov }
215 return $local_session;
216 }
217
218 1;