Revision 659 (by ahitrov, 2017/08/08 16:46:56) |
Facebook oAuth update
|
package session::AUTH::FaceBook;
use strict;
use warnings;
use LWP::UserAgent;
use JSON::XS;
use Data::Dumper;
use URI;
use URI::QueryParam;
use Encode;
use Contenido::Globals;
use vars qw($VERSION);
$VERSION = '4.1';
=for rem
facebook:
auto_create_user: 1
app_id: 15 decimal digits
app_secret: 32 hex digits
authorize_url: https://www.facebook.com/dialog/oauth
access_token_url: https://graph.facebook.com/oauth/access_token
user_info_url: https://graph.facebook.com/me
user_post_url: ~
state: is passed back to your app as a parameter of the redirect_uri when the user completed the authentication
store:
class: "+Comments::Authentication::Store"
type: facebook
=cut
our $JSON = JSON::XS->new->utf8;
=for rem SCHEMA
$m->redirect ( $fb_connect->authorize_url( facebook_redirect_uri => ... )->as_string );
=cut
sub new {
my ($class, %config) = @_;
my $self = bless {}, $class;
$self->{facebook_authorize_url} = 'https://www.facebook.com/dialog/oauth';
$self->{facebook_access_token_url} = 'https://graph.facebook.com/oauth/access_token';
$self->{facebook_user_info_url} = 'https://graph.facebook.com/me';
for (qw(facebook_app_id facebook_app_secret)) {
$self->{$_} = $config{$_} || $state->{session}{$_} || return undef;
}
$self->{timeout} = $state->{session}{connection_timeout} || 3;
for (qw(facebook_user_post_url facebook_redirect_uri facebook_scope)) {
$self->{$_} = $config{$_} || $state->{session}{$_};
}
if ( exists $self->{facebook_scope} && $self->{facebook_scope} ) {
@{$self->{facebook_fields}} = split /[\,\ ]+/, $self->{facebook_scope};
}
return $self;
}
sub authorize_url {
my $self = shift;
my (%args) = @_;
my $go = URI->new( $self->{facebook_authorize_url} );
$go->query_param( client_id => $self->{facebook_app_id} );
$go->query_param( state => $args{state} ) if $args{state};
$go->query_param( scope => $self->{facebook_scope} ) if $self->{facebook_scope};
$args{redirect_uri} ||= $self->{facebook_redirect_uri};
for ( keys %args ) {
$go->query_param( $_ => $args{$_} );
}
warn Dumper($go) if $DEBUG;
return $go;
}
sub authenticate {
my ( $self, %authinfo ) = @_;
warn "FB.authenticate" if $DEBUG;
# TODO: we need callback url
my $local_session = $session || $keeper->{session}->get_session;
my $redirect_uri = $self->{facebook_redirect_uri};
my $access_token = $local_session->{facebook_access_token};
my $expires = $local_session->{facebook_expires};
if ($access_token and $expires > time) {
warn "Already have access_token" if $DEBUG;
} else {
undef $access_token;
}
my $code = $authinfo{'code'};
unless ( $code ) {
warn "Call to authenticate without code";
return undef;
}
my $ua = LWP::UserAgent->new;
$ua->timeout($self->{timeout});
unless ($access_token) {
my $req = URI->new( $self->{facebook_access_token_url});
$req->query_param( client_id => $self->{facebook_app_id} );
$req->query_param( redirect_uri => $redirect_uri );
$req->query_param( client_secret=> $self->{facebook_app_secret} );
$req->query_param( code => $code);
warn "Get $req" if $DEBUG;
my $res = $ua->get($req);
unless ($res->code == 200) {
warn "access_token request failed: ".$res->status_line;
return undef;
}
my $token_response = eval { JSON::XS->new->utf8->decode($res->content) };
warn Dumper($token_response) if $DEBUG;
if ( ref $token_response eq 'HASH' && exists $token_response->{access_token} && $token_response->{access_token} ) {
$access_token = $token_response->{access_token};
} else {
warn "No access token in response: ".$res->content;
return undef;
}
$local_session->set( facebook_access_token => $access_token );
if( my $expires = $token_response->{expires_in} ) {
$local_session->set( facebook_expires => time + $expires );
}
warn "FB: requested access token" if $DEBUG;
} else {
warn "FB: have access token" if $DEBUG;
}
my $req = URI->new( $self->{facebook_user_info_url} );
my @req_fields = qw( id name gender location picture.width(720).height(720).as(picture_large) );
if ( exists $self->{facebook_fields} && ref $self->{facebook_fields} eq 'ARRAY' && @{$self->{facebook_fields}} ) {
push @req_fields, @{$self->{facebook_fields}};
}
$req->query_param( fields => join(',', @req_fields) );
$req->query_param( access_token => $access_token );
warn "Fetching user $req" if $DEBUG;
my $res = $ua->get($req);
unless ($res->code == 200) {
warn "user request failed: ".$res->status_line;
return undef;
}
my $info;
unless ( $info = eval { JSON::XS->new->utf8->decode($res->content) } ) {
warn "user '".$res->content."' decode failed: $@";
return undef;
}
warn "Userhash = ".Dumper($info) if $DEBUG;
#warn "facebook: user=$info->{name} / $info->{id} / $info->{gender}";
$local_session->delete( 'facebook_redirect_url' );
my @plugins = split (/[\ |\t]+/, $state->{plugins});
my $name = Encode::encode('utf-8', $info->{name});
my $email = exists $info->{email} && $info->{email} ? $info->{email} : undef;
my $avatar_url = exists $info->{picture_large} ? $info->{picture_large}{data}{url} : 'https://graph.facebook.com/'.$info->{id}.'/picture?type=large';
if ( grep { $_ eq 'users' } @plugins ) {
my $user;
if ( $state->{users}->use_credentials ) {
if ( $local_session->id ) {
$user = $keeper->{users}->get_profile( id => $local_session->{id} );
} else {
$user = $keeper->{users}->get_profile( facebook => $info->{id} );
}
}
if ( !ref $user && $email ) {
$user = $keeper->{users}->get_profile( email => $keeper->{users}->_email_reduction( $email ) );
}
unless ( ref $user ) {
$user = $keeper->{users}->get_profile( login => 'facebook:'.$info->{id} );
}
unless ( ref $user ) {
my $user_class = $state->{users}->profile_document_class;
$user = $user_class->new( $keeper );
my %props = map { $_->{attr} => $_ } $user->structure;
$user->login( $email || 'facebook:'.$info->{id} );
$user->name( $name );
$user->status( 1 );
$user->type( 0 );
$user->login_method('facebook');
if ( exists $props{country} ) {
$user->country( $info->{locale} );
}
$user->email( $email );
my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
if ( ref $prop_ava ) {
my $avatar = $user->_store_image( $avatar_url, attr => 'avatar' );
$user->avatar( $user->_serialize($avatar) );
}
$user->store;
} else {
my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
if ( ref $prop_ava ) {
my $avatar = $user->get_image( 'avatar' );
unless ( ref $avatar && exists $avatar->{filename} ) {
my $avatar = $user->_store_image( $avatar_url, attr => 'avatar' );
$user->avatar( $user->_serialize($avatar) );
$user->store;
}
}
}
if ( $state->{users}->use_credentials ) {
$user->create_credential(
facebook => $info->{id},
avatar => $avatar_url,
);
if ( exists $info->{email} && $info->{email} ) {
$user->create_credential(
email => $info->{email},
status => 1,
);
}
}
my %data = session::Keeper::_get_hash_from_profile( $user );
$data{auth_by} = 'facebook';
$data{avatar} ||= 'https://graph.facebook.com/'.$info->{id}.'/picture?type=square';
$data{email} ||= $email if $email;
$local_session->set( %data );
} else {
my %data = (
id => $info->{id},
name => $name,
login => 'facebook:'.$info->{id},
status => 1,
type => 0,
auth_by => 'facebook',
ltime => time,
avatar => 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large',
);
$data{email} = $email if $email;
$local_session->set ( %data );
}
return $local_session;
}
1;