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 $m->redirect ( $fb_connect->fb_authorize_url( redirect_uri => ... ) );
37
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 sub fb_authorize_url {
61 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 $keeper->{session}->store_value( facebook_access_token => $access_token );
115 $local_session->{facebook_access_token} = $access_token;
116 if( my $expires = $res{expires} ) {
117 $local_session->{facebook_expires} = time + $expires;
118 $keeper->{session}->store_value( facebook_expires => $local_session->{facebook_expires} );
119 } else {
120 #$c->user_session->{'expires'} = time + 3600*24;
121 }
122 239 ahitrov warn "FB: requested access token" if $DEBUG;
123 197 ahitrov } else {
124 239 ahitrov warn "FB: have access token" if $DEBUG;
125 197 ahitrov }
126
127 my $req = URI->new( $self->{facebook_user_info_url} );
128 $req->query_param( access_token => $access_token );
129
130 239 ahitrov warn "Fetching user $req" if $DEBUG;
131 197 ahitrov my $res = $ua->get($req);
132 unless ($res->code == 200) {
133 warn "user request failed: ".$res->status_line;
134 return undef;
135 }
136 my $info;
137 unless ( $info = eval { JSON::XS->new->utf8->decode($res->content) } ) {
138 warn "user '".$res->content."' decode failed: $@";
139 return undef;
140 }
141 239 ahitrov warn "Userhash = ".Dumper($info) if $DEBUG;
142 197 ahitrov #warn "facebook: user=$info->{name} / $info->{id} / $info->{gender}";
143
144 $keeper->{session}->delete_key( 'facebook_redirect_url' );
145 delete $local_session->{facebook_redirect_url};
146
147 my @plugins = split (/[\ |\t]+/, $state->{plugins});
148 if ( grep { $_ eq 'users' } @plugins ) {
149 my $user = $keeper->{users}->get_profile( login => 'facebook:'.$info->{id} );
150 unless ( ref $user ) {
151 my $user_class = $state->{users}->profile_document_class;
152 $user = $user_class->new( $keeper );
153 $user->login( 'facebook:'.$info->{id} );
154 my $name = Encode::encode('utf-8', $info->{name});
155 $user->name( $name );
156 $user->status( 1 );
157 $user->type( 0 );
158 $user->login_method('facebook');
159 $user->country( $info->{locale} );
160 $user->email( undef );
161 239 ahitrov
162 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
163 if ( ref $prop_ava ) {
164 my $avatar = $user->_store_image( 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large', attr => 'avatar' );
165 $user->avatar( $user->_serialize($avatar) );
166 }
167
168 197 ahitrov $user->store;
169 239 ahitrov } else {
170 my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
171 if ( ref $prop_ava ) {
172 my $avatar = $user->get_image( 'avatar' );
173 unless ( ref $avatar && exists $avatar->{filename} ) {
174 my $avatar = $user->_store_image( 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large', attr => 'avatar' );
175 $user->avatar( $user->_serialize($avatar) );
176 $user->store;
177 }
178 }
179 197 ahitrov }
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 239 ahitrov avatar => 'https://graph.facebook.com/'.$info->{username}.'/picture',
188 197 ahitrov );
189 $keeper->{session}->store_value ( %data );
190 while ( my ( $key, $value ) = each %data ) {
191 $local_session->{$key} = $value;
192 }
193 }
194 return $local_session;
195 }
196
197 1;