Line # Revision Author
1 194 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 243 ahitrov app_id: 15 decimal digits
20 app_secret: 32 hex digits
21 authorize_url: https://www.facebook.com/dialog/oauth
22 194 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 243 ahitrov state: is passed back to your app as a parameter of the redirect_uri when the user completed the authentication
26 194 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 243 ahitrov $m->redirect ( $fb_connect->authorize_url( facebook_redirect_uri => ... )->as_string );
37 194 ahitrov
38
39 =cut
40
41
42 sub new {
43 my ($class, %config) = @_;
44 my $self = bless {}, $class;
45 243 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 $self->{$_} = $config{$_} || $state->{session}{$_} || return undef;
52 194 ahitrov }
53 243 ahitrov $self->{timeout} = $state->{session}{connection_timeout} || 3;
54 465 ahitrov for (qw(facebook_user_post_url facebook_redirect_uri facebook_scope)) {
55 243 ahitrov $self->{$_} = $config{$_} || $state->{session}{$_};
56 194 ahitrov }
57 return $self;
58 }
59
60 243 ahitrov sub authorize_url {
61 194 ahitrov my $self = shift;
62 my (%args) = @_;
63 my $go = URI->new( $self->{facebook_authorize_url} );
64 243 ahitrov $go->query_param( client_id => $self->{facebook_app_id} );
65 $go->query_param( state => $args{state} ) if $args{state};
66 465 ahitrov $go->query_param( scope => $self->{facebook_scope} ) if $self->{facebook_scope};
67 194 ahitrov $args{redirect_uri} ||= $self->{facebook_redirect_uri};
68 for ( keys %args ) {
69 $go->query_param( $_ => $args{$_} );
70 }
71 243 ahitrov warn Dumper($go) if $DEBUG;
72 194 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 243 ahitrov my $redirect_uri = $self->{facebook_redirect_uri};
81 194 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 243 ahitrov warn "Get $req" if $DEBUG;
103 194 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 243 ahitrov warn Dumper(\%res) if $DEBUG;
110 194 ahitrov unless ($access_token = $res{access_token}) {
111 warn "No access token in response: ".$res->content;
112 return undef;
113 }
114 281 ahitrov $local_session->set( facebook_access_token => $access_token );
115 194 ahitrov if( my $expires = $res{expires} ) {
116 281 ahitrov $local_session->set( facebook_expires => time + $expires );
117 194 ahitrov } else {
118 281 ahitrov #$local_session->set( facebook_expires => time + 3600*24 );
119 194 ahitrov }
120 243 ahitrov warn "FB: requested access token" if $DEBUG;
121 194 ahitrov } else {
122 243 ahitrov warn "FB: have access token" if $DEBUG;
123 194 ahitrov }
124
125 my $req = URI->new( $self->{facebook_user_info_url} );
126 $req->query_param( access_token => $access_token );
127
128 243 ahitrov warn "Fetching user $req" if $DEBUG;
129 194 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 243 ahitrov warn "Userhash = ".Dumper($info) if $DEBUG;
140 194 ahitrov #warn "facebook: user=$info->{name} / $info->{id} / $info->{gender}";
141
142 281 ahitrov $local_session->delete( 'facebook_redirect_url' );
143 243 ahitrov
144 194 ahitrov my @plugins = split (/[\ |\t]+/, $state->{plugins});
145 243 ahitrov my $name = Encode::encode('utf-8', $info->{name});
146 Encode::from_to( $name, 'utf-8', 'koi8-r' );
147 194 ahitrov if ( grep { $_ eq 'users' } @plugins ) {
148 my $user = $keeper->{users}->get_profile( login => 'facebook:'.$info->{id} );
149 unless ( ref $user ) {
150 my $user_class = $state->{users}->profile_document_class;
151 $user = $user_class->new( $keeper );
152 $user->login( 'facebook:'.$info->{id} );
153 $user->name( $name );
154 $user->status( 1 );
155 $user->type( 0 );
156 $user->login_method('facebook');
157 $user->country( $info->{locale} );
158 $user->email( undef );
159
160 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
161 if ( ref $prop_ava ) {
162 my $avatar = $user->_store_image( 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large', attr => 'avatar' );
163 local $Data::Dumper::Indent = 0;
164 $user->avatar( Data::Dumper::Dumper($avatar) );
165 }
166
167 $user->store;
168 } else {
169 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
170 if ( ref $prop_ava ) {
171 my $avatar = $user->get_image( 'avatar' );
172 unless ( ref $avatar && exists $avatar->{filename} ) {
173 my $avatar = $user->_store_image( 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large', attr => 'avatar' );
174 local $Data::Dumper::Indent = 0;
175 $user->avatar( Data::Dumper::Dumper($avatar) );
176 $user->store;
177 }
178 }
179 }
180 my %data = (
181 id => $user->id,
182 name => $user->name,
183 login => $user->login,
184 status => $user->status,
185 type => $user->type,
186 ltime => time,
187 avatar => 'https://graph.facebook.com/'.$info->{username}.'/picture',
188 );
189 281 ahitrov $local_session->set( %data );
190 243 ahitrov } else {
191 my %data = (
192 id => $info->{id},
193 name => $name,
194 login => 'facebook:'.$info->{id},
195 status => 1,
196 type => 0,
197 auth_by => 'facebook',
198 ltime => time,
199 avatar => 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large',
200 );
201 281 ahitrov $local_session->set ( %data );
202 194 ahitrov }
203 return $local_session;
204 }
205
206 1;