Revision 191 (by ahitrov, 2012/03/15 18:22:24) Session plug-in
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: 122117614500563
	app_key: 3da06301715b0efc5c873535c56c2c33
	app_secret: 656bd1369486b902e9bf831a9a08132b
	authorize_url: https://graph.facebook.com/oauth/authorize
	access_token_url: https://graph.facebook.com/oauth/access_token
	user_info_url: https://graph.facebook.com/me
	user_post_url: ~
	store:
		class: "+Comments::Authentication::Store"
		type: facebook

=cut

our $JSON = JSON::XS->new->utf8;

=for rem SCHEMA

$m->redirect ( $fb_connect->fb_authorize_url( redirect_uri => ... ) );


=cut


sub new {
	my ($class, %config) = @_;
	my $self = bless {}, $class;
	for (qw(facebook_app_id facebook_app_key facebook_app_secret facebook_authorize_url facebook_access_token_url facebook_user_info_url)) {
		$self->{$_} = $config{$_} || $state->{session}->{$_} || return undef;
	}
	$self->{timeout} = $state->{session}->{connection_timeout} || 3;
	for (qw(facebook_user_post_url facebook_redirect_uri)) {
		$self->{$_} = $config{$_} || $state->{session}->{$_};
	}
	return $self;
}

sub fb_authorize_url {
	my $self = shift;
	my (%args) = @_;
	my $go = URI->new( $self->{facebook_authorize_url} );
	warn Dumper($go);
	$go->query_param( client_id => $self->{facebook_app_key} );
	$go->query_param( scope => "publish_stream" );
	$args{redirect_uri} ||= $self->{facebook_redirect_uri};
	for ( keys %args ) {
		$go->query_param( $_ => $args{$_} );
	}
	$keeper->{session}->store_value( facebook_redirect_url => $self->{facebook_redirect_uri} );
	return $go;
}

sub authenticate {
	my ( $self, %authinfo ) = @_;
	warn "FB.authenticate"					if $DEBUG;
	# TODO: we need callback url
	#warn "user_session=".dumper( $c->user_session )." ";
	my $local_session = $session || $keeper->{session}->get_session;
	my $redirect_uri = $local_session->{facebook_redirect_url};
	
	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";
		my $res = $ua->get($req);
		unless ($res->code == 200) {
			warn "access_token request failed: ".$res->status_line;
			return undef;
		}
		my %res = eval { URI->new("?".$res->content)->query_form };
		warn Dumper(\%res);
		unless ($access_token = $res{access_token}) {
			warn "No access token in response: ".$res->content;
			return undef;
		}
		$keeper->{session}->store_value( facebook_access_token => $access_token );
		$local_session->{facebook_access_token} = $access_token;
		if( my $expires = $res{expires} ) {
			$local_session->{facebook_expires} = time + $expires;
			$keeper->{session}->store_value( facebook_expires => $local_session->{facebook_expires} );
		} else {
			#$c->user_session->{'expires'} = time + 3600*24;
		}
		warn "FB: requested access token";
	} else {
		warn "FB: have access token";
	}
	
	my $req = URI->new( $self->{facebook_user_info_url} );
	$req->query_param( access_token => $access_token );
	
	warn "Fetching user $req";
	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);
	#warn "facebook: user=$info->{name} / $info->{id} / $info->{gender}";

	my @plugins = split (/[\ |\t]+/, $state->{plugins});
	if ( grep { $_ eq 'users' } @plugins ) {
		my $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 );
			$user->login( 'facebook:'.$info->{id} );
			my $name = Encode::encode('utf-8', $info->{name});
			Encode::from_to( $name, 'utf-8', 'koi8-r' );
			$user->name( $name );
			$user->status( 1 );
			$user->type( 0 );
			$user->login_method('facebook');
			$user->country( $info->{locale} );
			$user->email( undef );

			my ($prop_ava) = grep { $_->{attr} eq 'avatar' && $_->{type} eq 'image' } $user->structure;
			if ( ref $prop_ava ) {
				my $avatar = $user->_store_image( 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large', attr => 'avatar' );
				local $Data::Dumper::Indent = 0;
				$user->avatar( Data::Dumper::Dumper($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( 'https://graph.facebook.com/'.$info->{username}.'/picture?type=large', attr => 'avatar' );
					local $Data::Dumper::Indent = 0;
					$user->avatar( Data::Dumper::Dumper($avatar) );
					$user->store;
				}
			}
		}
		my %data = (
			id	=> $user->id,
			name	=> $user->name,
			login	=> $user->login,
			status	=> $user->status,
			type	=> $user->type,
			ltime   => time,
			avatar	=> 'https://graph.facebook.com/'.$info->{username}.'/picture',
		);
		$keeper->{session}->store_value ( %data );
		while ( my ( $key, $value ) = each %data ) {
			$local_session->{$key} = $value;
		}
	}
	return $local_session;
}

1;

Небольшая справка по веткам

cnddist – контейнер, в котором хранятся все дистрибутивы всех библиотек и программных пакетов, которые использовались при построении различных версий Contenido. Если какой-то библиотеки в данном хранилище нет, инсталлятор сделает попытку "подтянуть" ее с веба (например, с CPAN). Если библиотека слишком старая, есть очень большая вероятность, что ее там уже нет. Поэтому мы храним весь хлам от всех сборок. Если какой-то дистрибутив вдруг отсутствует в cnddist - напишите нам, мы положим его туда.

koi8 – отмирающая ветка, чей код, выдача и все внутренние библиотеки заточены на кодировку KOI8-R. Вносятся только те дополнения, которые касаются внешнего вида и функционала админки, баги ядра, обязательные обновления портов и мелочи, которые легко скопипастить. В дальнейшем планируется полная остановка поддержки по данной ветке.

utf8 – актуальная ветка, заточенная под UTF-8.

Внутри каждой ветки: core – исходники ядра; install – скрипт установки инсталляции; plugins – плагины; samples – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.