diff --git a/docs/changelog/8.x.x.txt b/docs/changelog/8.x.x.txt index 25798f40d..1016de841 100644 --- a/docs/changelog/8.x.x.txt +++ b/docs/changelog/8.x.x.txt @@ -1,4 +1,5 @@ 8.0.0 - Replaced the existing caching mechanism with memcached, which results in a 400% improvement to cache speed. See migration.txt for API changes and gotcha.txt for prereq changes. - Added "hot sessions" so sessions interact with the database less. + - Added Facebook Auth and FacebookLogin macro. diff --git a/docs/gotcha.txt b/docs/gotcha.txt index 8cffc0eee..ae71780d9 100644 --- a/docs/gotcha.txt +++ b/docs/gotcha.txt @@ -16,6 +16,7 @@ save you many hours of grief. * WebGUI now requires the following modules - Moose - CHI + - Facebook::Graph 7.9.8 -------------------------------------------------------------------- diff --git a/etc/WebGUI.conf.original b/etc/WebGUI.conf.original index 6a4ee6f4a..03e50b33f 100644 --- a/etc/WebGUI.conf.original +++ b/etc/WebGUI.conf.original @@ -190,7 +190,7 @@ # List the authentication plug-ins you wish to be available on # this site. -"authMethods" : [ "LDAP", "WebGUI" ], +"authMethods" : [ "LDAP", "WebGUI", "Twitter", "Facebook"], # List the merchant gateways you have installed and wish to be # available on this site. @@ -821,6 +821,7 @@ "EditableToggle" : "EditableToggle", "e" : "e_companyEmail", "Extras" : "Extras", + "FacebookLogin" : "FacebookLogin", "FetchMimeType" : "FetchMimeType", "FilePump" : "FilePump", "FileUrl" : "FileUrl", diff --git a/lib/WebGUI/Auth/Facebook.pm b/lib/WebGUI/Auth/Facebook.pm new file mode 100644 index 000000000..3801e0950 --- /dev/null +++ b/lib/WebGUI/Auth/Facebook.pm @@ -0,0 +1,295 @@ +package WebGUI::Auth::Facebook; + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2010 Plain Black Corporation. + ------------------------------------------------------------------- + Please read the legal notices (docs/legal.txt) and the license + (docs/license.txt) that came with this distribution before using + this software. + ------------------------------------------------------------------- + http://www.plainblack.com info@plainblack.com + ------------------------------------------------------------------- + +=cut + +use strict; +use base 'WebGUI::Auth'; +use Facebook::Graph; + +=head1 NAME + +WebGUI::Auth::Facebook -- Facebook auth for WebGUI + +=head1 DESCRIPTION + +Allow Facebook users to authenticate to WebGUI + +=head1 METHODS + +These methods are available from this class: + +=cut + +#---------------------------------------------------------------------------- + +=head2 new ( ... ) + +Create a new object + +=cut + +sub new { + my $self = shift->SUPER::new(@_); + return bless $self, __PACKAGE__; # Auth requires rebless +} + +#---------------------------------------------------------------------------- + +=head2 createFacebookUser ( fbuser ) + + my $user = $self->createFacebookUser( $fb->fetch('me') ); + +Create a new Facebook::Graph user. + +=cut + +sub createFacebookUser { + my ( $self, $fbuser ) = @_; + my $user = WebGUI::User->create( $self->session ); + $user->username( $fbuser->{name} ); + $user->profileField('email', $fbuser->{email}); + $user->profileField('firstName', $fbuser->{first_name}); + $user->profileField('lastName', $fbuser->{last_name}); + $self->saveParams( $user->userId, $self->authMethod, { + "facebookUserId" => $fbuser->{id}, + } ); + return $user; +} + +#---------------------------------------------------------------------------- + +=head2 editUserSettingsForm ( ) + +Return the form to edit the settings of this Auth module + +=cut + +sub editUserSettingsForm { + my $self = shift; + my $session = $self->session; + my ( $setting ) = $session->quick(qw( setting )); + my $i18n = WebGUI::International->new( $session, 'Auth_Facebook' ); + + my $f = WebGUI::HTMLForm->new( $session ); + + $f->yesNo( + name => 'facebookAuthEnabled', + value => $setting->get( 'facebookAuthEnabled' ), + label => $i18n->get('enabled'), + hoverHelp => $i18n->get('enabled help'), + ); + + $f->text( + name => 'facebookAuthAppId', + value => $setting->get( 'facebookAuthAppId' ), + label => $i18n->get('app id'), + hoverHelp => $i18n->get('app id help'), + subtext => $i18n->get('get app id'), + ); + + $f->text( + name => 'facebookAuthSecret', + value => $setting->get( 'facebookAuthSecret' ), + label => $i18n->get('secret'), + hoverHelp => $i18n->get('secret help'), + ); + + $f->template( + name => 'facebookAuthTemplateIdChooseUsername', + value => $setting->get( 'facebookAuthTemplateIdChooseUsername' ), + label => $i18n->get('choose username template'), + hoverHelp => $i18n->get('choose username template help'), + namespace => 'Auth/Facebook/ChooseUsername', + ); + + return $f->printRowsOnly; +} + +#---------------------------------------------------------------------------- + +=head2 editUserSettingsFormSave ( ) + +Process the form for this Auth module's settings + +=cut + +sub editUserSettingsFormSave { + my $self = shift; + my $session = $self->session; + my ( $form, $setting ) = $session->quick(qw( form setting )); + + my @fields = qw( + facebookAuthEnabled facebookAuthAppId facebookAuthSecret + facebookAuthTemplateIdChooseUsername + ); + for my $field ( @fields ) { + $setting->set( $field, $form->get( $field ) ); + } + + return; +} + +#---------------------------------------------------------------------------- + +=head2 getTemplateChooseUsername ( ) + +Get the template to choose a username + +=cut + +sub getTemplateChooseUsername { + my ( $self ) = @_; + my $templateId = $self->session->setting->get('facebookAuthTemplateIdChooseUsername'); + return WebGUI::Asset->newById( $self->session, $templateId ); +} + +#---------------------------------------------------------------------------- + +=head2 getFacebook ( ) + +Get the Facebook::Graph object with the appropriate keys + +=cut + +sub getFacebook { + my ( $self ) = @_; + my ( $url, $scratch, $setting ) = $self->session->quick( qw( url scratch setting ) ); + if ( !$self->{_fb} ) { + my $fb = Facebook::Graph->new( + app_id => $setting->get( 'facebookAuthAppId' ), + secret => $setting->get( 'facebookAuthSecret' ), + postback => $url->getSiteURL . $url->page('op=auth&authType=Facebook&method=callback'), + ); + if ($scratch->get('facebookAuthAccessToken')) { + $fb->access_token($scratch->get('facebookAuthAccessToken')); + } + $self->{_fb} = $fb; + } + return $self->{_fb}; +} + +#---------------------------------------------------------------------------- + +=head2 www_login ( ) + +Begin the login procedure + +=cut + +sub www_login { + my ( $self ) = @_; + my $session = $self->session; + my ( $url, $scratch, $setting ) = $session->quick( qw( url scratch setting ) ); + + my $auth_url = $self->getFacebook + ->authorize + ->extend_permissions(qw(email)) + ->uri_as_string; + + $session->http->setRedirect($auth_url); + return "redirect"; +} + +#---------------------------------------------------------------------------- + +=head2 www_callback ( ) + +Callback from the Facebook authentication. Try to log the user in, creating a +new user account if necessary. + +If the username is taken, allow the user to choose a new one. + +=cut + +sub www_callback { + my ( $self ) = @_; + my $session = $self->session; + my ( $form, $scratch, $db, $setting ) = $session->quick(qw( form scratch db setting )); + + # handle facebook stuff + my $fb = $self->getFacebook; + $fb->request_access_token($form->get('code')); + $scratch->set('facebookAuthAccessToken', $fb->access_token); + my $fbuser = $fb->fetch('me'); + + + ### Log the user in + # Find their FB user ID + my $userId = $db->quickScalar( + "SELECT userId FROM authentication WHERE authMethod = ? AND fieldName = ? AND fieldData = ?", + [ "Facebook", "facebookUserId", $fbuser->{id} ], + ); + + # Returning user + if ( $userId ) { + my $user = WebGUI::User->new( $session, $userId ); + $self->user( $user ); + return $self->login; + } + # Otherwise see if their screen name exists and create a user + elsif ( !WebGUI::User->newByUsername( $session, $fbuser->{name}) ) { + my $user = $self->createFacebookUser( $fbuser ); + $self->user( $user ); + return $self->login; + } + + # Otherwise ask them for a new username to use + my $i18n = WebGUI::International->new( $session, 'Auth_Facebook' ); + my $tmpl = $self->getTemplateChooseUsername; + my $var = { + message => sprintf( $i18n->get("username taken"), $fbuser->{name} ), + }; + + return $tmpl->process( $var ); +} + +#---------------------------------------------------------------------------- + +=head2 www_setUsername ( ) + +Set the username for a fb user. Only used as part of the initial fb +registration. + +=cut + +sub www_setUsername { + my ( $self ) = @_; + my $session = $self->session; + my ( $form, $scratch, $db ) = $session->quick(qw( form scratch db )); + my $i18n = WebGUI::International->new( $session, 'Auth_Facebook' ); + + # Don't allow just anybody to set a username + my $fb = $self->getFacebook; + return if $fb->access_token eq ''; + + my $fbuser = $fb->fetch('me'); + $fbuser->{name} = $form->get('newUsername'); + + if ( !WebGUI::User->newByUsername( $session, $fbuser->{name} ) ) { + my $user = $self->createFacebookUser( $fbuser ); + $self->user( $user ); + return $self->login; + } + + # Username is again taken! Noooooo! + my $tmpl = $self->getTemplateChooseUsername; + my $var = { + message => sprintf( $i18n->get("username taken"), $fbuser->{name} ), + }; + + return $tmpl->process( $var ); +} + +1; diff --git a/lib/WebGUI/Macro/FacebookLogin.pm b/lib/WebGUI/Macro/FacebookLogin.pm new file mode 100644 index 000000000..214a427a1 --- /dev/null +++ b/lib/WebGUI/Macro/FacebookLogin.pm @@ -0,0 +1,37 @@ +package WebGUI::Macro::FacebookLogin; + +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2010 Plain Black Corporation. +#------------------------------------------------------------------- +# Please read the legal notices (docs/legal.txt) and the license +# (docs/license.txt) that came with this distribution before using +# this software. +#------------------------------------------------------------------- +# http://www.plainblack.com info@plainblack.com +#------------------------------------------------------------------- + +use strict; + +=head1 NAME + +Package WebGUI::Macro::FacebookLogin + +=head1 DESCRIPTION + +Works with the Facebook Auth plugin to allow users to log in using facebook. + +=cut + + +#------------------------------------------------------------------- +sub process { + my $session = shift; + my $url = $session->url; + return sprintf 'login with Facebook', + $url->page('op=auth;authType=Facebook;method=login'), + $url->extras('macro/FacebookLogin/login-button.png'); +} + +1; + +#vim:ft=perl diff --git a/lib/WebGUI/i18n/English/Auth_Facebook.pm b/lib/WebGUI/i18n/English/Auth_Facebook.pm new file mode 100644 index 000000000..70d2c0f21 --- /dev/null +++ b/lib/WebGUI/i18n/English/Auth_Facebook.pm @@ -0,0 +1,74 @@ +package WebGUI::i18n::English::Auth_Facebook; + +use strict; + +our $I18N = { + 'enabled' => { + message => q{Enabled}, + lastUpdated => 0, + context => q{Label for auth setting field}, + }, + + 'enabled help' => { + message => q{Enabled Facebook-based login}, + lastUpdated => 0, + context => q{Hover help for auth setting field}, + }, + + 'get app id' => { + message => q{Get a Facebook App Id from http://apps.facebook.com/developer}, + lastUpdated => 0, + context => q{Link to get a Facebook App Id}, + }, + + 'app id' => { + message => q{Application Id}, + lastUpdated => 0, + context => q{Label for auth setting field}, + }, + + 'app id help' => { + message => q{The Application ID from your Facebook application settings}, + lastUpdated => 0, + context => q{Hover help for auth setting field}, + }, + + 'secret' => { + message => q{Application Secret}, + lastUpdated => 0, + context => q{Label for auth setting field}, + }, + + 'secret help' => { + message => q{The Facebook Application Secret from your application settings}, + lastUpdated => 0, + context => q{Hover help for auth setting field}, + }, + + 'choose username title' => { + message => q{Choose a Username}, + lastUpdated => 0, + context => q{Title for screen to choose a username}, + }, + + 'username taken' => { + message => q{That username "%s" is taken. Please choose a new username.}, + lastUpdated => 0, + context => q{An error message for the choose a username screen}, + }, + + 'choose username template' => { + message => q{Choose Username Template}, + lastUpdated => 0, + context => q{Label for auth setting field}, + }, + + 'choose username template help' => { + message => q{The template to choose a username if the user's screen name already exists}, + lastUpdated => 0, + context => q{Hover help for auth setting field}, + }, +}; + +1; +#vim:ft=perl diff --git a/sbin/testEnvironment.pl b/sbin/testEnvironment.pl index 3535c58ea..ac8c71892 100755 --- a/sbin/testEnvironment.pl +++ b/sbin/testEnvironment.pl @@ -157,6 +157,7 @@ checkModule('HTTP::Exception', ); checkModule('Net::Twitter', "3.13006" ); checkModule('Number::Format', ); checkModule('Email::Valid', ); +checkModule('Facebook::Graph', '0.0505' ); failAndExit("Required modules are missing, running no more checks.") if $missingModule; diff --git a/share/upgrades/7.9.12-8.0.0/facebook_auth.sql b/share/upgrades/7.9.12-8.0.0/facebook_auth.sql new file mode 100644 index 000000000..2aedfa326 --- /dev/null +++ b/share/upgrades/7.9.12-8.0.0/facebook_auth.sql @@ -0,0 +1,4 @@ +insert into settings values ('facebookAuthEnabled',0); +insert into settings values ('facebookAuthAppId',''); +insert into settings values ('facebookAuthSecret',''); +insert into settings values ('facebookAuthTemplateIdChooseUsername','anlFXped9lqXPThZTdFX0A'); \ No newline at end of file diff --git a/share/upgrades/7.9.12-8.0.0/root_import_default-facebook-choose-username.wgpkg b/share/upgrades/7.9.12-8.0.0/root_import_default-facebook-choose-username.wgpkg new file mode 100644 index 000000000..3b9bb8ef2 Binary files /dev/null and b/share/upgrades/7.9.12-8.0.0/root_import_default-facebook-choose-username.wgpkg differ diff --git a/www/extras/macro/FacebookLogin/login-button.png b/www/extras/macro/FacebookLogin/login-button.png new file mode 100644 index 000000000..4e7766bca Binary files /dev/null and b/www/extras/macro/FacebookLogin/login-button.png differ