Enabled Fastbook/OAuth login

This commit is contained in:
Deon George 2013-05-23 17:00:14 +10:00
parent 84a7752ef2
commit 8edac5ad4a
15 changed files with 2071 additions and 2 deletions

View File

@ -110,6 +110,7 @@ Kohana::$config->attach(new Config_File);
*/
Kohana::modules(array(
'lnapp' => MODPATH.'lnApp',
'oauth' => MODPATH.'oauth', // OAuth Module for External Authentication
'auth' => SMDPATH.'auth', // Basic authentication
'cache' => SMDPATH.'cache', // Caching with multiple backends
'cron' => SMDPATH.'cron', // Kohana Cron Module

View File

@ -19,7 +19,7 @@ class URL extends Kohana_URL {
);
public static function admin_url() {
return (Request::current() AND ((Auth::instance()->logged_in() AND ! empty(URL::$method_directory[strtolower(Request::current()->directory())])) OR in_array(strtolower(Request::current()->controller()),array('login'))));
return (Request::current() AND ((Auth::instance()->logged_in() AND ! empty(URL::$method_directory[strtolower(Request::current()->directory())])) OR in_array(strtolower(Request::current()->controller()),array('login','oauth'))));
}
/**

@ -1 +1 @@
Subproject commit 41e777bd9d85fdc9d32a3aa22f916d0d94530048
Subproject commit ab1adad456578500a4be8edd8635c560e25417f7

View File

@ -0,0 +1,27 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class overrides Kohana's Auth so that we can call a specific
* Authentication driver.
*
* @package OAuth
* @category Modifications
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
abstract class Auth extends Kohana_Auth {
public static function instance($type=NULL) {
if (is_null($type) OR (! $type instanceof Model_Oauth))
return parent::instance();
// Set the session class name
$class = 'Auth_'.ucfirst($type->name);
// Create a new session instance
Auth::$_instance = new $class($type);
return Auth::$_instance;
}
}
?>

View File

@ -0,0 +1,141 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides Authentication using Facebook
*
* @package OAuth
* @category Classes
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Auth_Facebook extends Auth_ORM {
// Our Facebook config data
private $config;
private $data;
// Our Facebook Object
private $fb;
private $me;
// Our OAuth Object
private $oo;
// Facebook UID
private $uid;
/**
* Perform the login processing
*
* We ignore password, since it is required in the parent(), we dont need it in Oauth
*/
protected function _login($user,$password,$remember) {
$this->complete_login($user);
if ($remember) {
$aoo = ORM::factory('Account_Oauth',array('account_id'=>$user->id));
// Record our user in the DB
$aoo->account_id = $user->id;
$aoo->oauth_id = $this->oo->id;
$aoo->userid = $remember->user_id();
switch ($this->oo->name) {
case 'facebook':
$aoo->oauth_data = $remember->fb->getAccessToken();
break;
}
return $aoo->save();
}
}
public function __construct(Model_Oauth $oo) {
include Kohana::find_file('vendor', 'facebook');
$this->oo = $oo;
// Load configuration "config/facebook"
$this->config = Kohana::$config->load('facebook');
parent::__construct((array)Kohana::$config->load('auth'));
// Create new Facebook object
$this->fb = new Facebook(array(
'appId' => $oo->app_id,
'secret' => $oo->secret,
'cookie' => $this->config->cookie,
'session_type' => $this->config->session_type,
));
try {
$this->me = $this->fb->api('/' . $this->fb->getUser(), 'GET');
} catch (FacebookApiException $e) {
// Do nothing.
}
}
/**
* Returns user data, default in case of failure.
*
* @param $key
* @param null $default
* @return mixed
* @throws FacebookApiException
*/
public function get($key,$default=NULL) {
if (! $uid = $this->user_id()) {
$this->login_url();
throw new FacebookApiException('User is not logged in.');
}
if (empty($this->data))
$this->data = $this->fb->api(array(
'method' => 'fql.query',
'query' => sprintf('SELECT %s FROM user WHERE uid = %s',$this->config_fields,$uid),
));
return (! empty($this->data[0][$key])) ? $this->data[0][$key] : $default;
}
/**
* Is user currently logged into facebook?
*/
public function logged_in($role=NULL,$debug=NULL) {
return $this->fb->getUser() ? TRUE : FALSE;
}
/**
* Creates a login url, based on scope, redirect_uri and display.
*
* @return string
*/
public function login_url() {
return urldecode($this->fb->getLoginUrl(array(
'scope' => $this->config->scope,
'redirect_uri' => $this->config->redirect_uri,
'display' => $this->config->display,
)));
}
/**
* Creates a logout url based on next.
*
* @return string
*/
public function logout_url() {
return urldecode($this->fb->getLogoutUrl(array('next'=>$this->config->next)));
}
/**
* Return user id if success, otherwise FALSE.
*/
public function user_id() {
if ($this->logged_in()) {
$this->uid = $this->fb->getUser();
return $this->uid;
} else {
return FALSE;
}
}
}
?>

View File

@ -0,0 +1,91 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides oauth capability
*
* @package OAuth
* @category Controllers
* @author Deon George
* @copyright (c) 2009-2013 Deon George
* @license http://dev.leenooks.net/license.html
*/
class Controller_Oauth extends Controller_TemplateDefault {
protected $auth_required = FALSE;
protected $secure_actions = array(
'link'=>TRUE,
);
public function action_login() {
// Make sure we are called with a valid oauth plugin
$oo = ORM::factory('Oauth',array('name'=>$this->request->param('id')));
if (! $oo->loaded() OR ! $oo->status)
HTTP::redirect('login');
$auth = NULL;
if ($oo->name == 'facebook') {
// User Denied a Facebook authorisation, so we'll go back to login
// We wouldnt normally get here, since we are doing JS authentication
if ($this->request->query('error') AND $this->request->query('error_reason') == 'user_denied')
HTTP::redirect('login');
$auth = Auth::instance($oo);
// If we are not logged in, do the facebook stuff.
// We wouldnt normally get here, since we are doing JS authentication
if (! $auth->logged_in())
HTTP::redirect($auth->login_url());
// Here we must be logged in to Facebook
// @todo Only use verified accounts - is this applicable?
$aoo = $oo->account_oauth->where('userid','=',$auth->user_id())->find();
}
// If we have an ID, we have been linked, redirect to login
if ($aoo->loaded() AND $auth->login($aoo->account,$auth->user_id(),$auth))
return $this->login();
// We need to link the ID
Session::instance()->set('login-no-oauth',TRUE);
Style::factory()
->type('file')
->data('media/theme/baseadmin/css/pages/login.css');
$this->template->content = View::factory('oauth/link')
->set('type',$oo->name);
$this->template->shownavbar = FALSE;
}
public function action_link() {
// Make sure we are called with a valid oauth plugin
$oo = ORM::factory('Oauth',array('name'=>$this->request->param('id')));
if (! $oo->loaded() OR ! $oo->status)
HTTP::redirect('login');
// Since we have logged in, get our user details
$ao = Auth::instance()->get_user();
$auth = Auth::instance($oo);
if (! $auth->logged_in())
HTTP::redirect('login');
if ($auth->login($ao,$auth->user_id(),$auth))
return $this->login();
}
/**
* When our login is complete and satisified, we execute here
*/
private function login() {
// Redirect to the user account
if ($redir = Session::instance()->get('afterlogin')) {
Session::instance()->delete('afterlogin');
HTTP::redirect($redir);
} else
HTTP::redirect(URL::link('user','welcome/index'));
}
}
?>

View File

@ -0,0 +1,18 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* OSB OAuth Model
*
* @package OAuth
* @category Models
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Model_Account_Oauth extends ORM_OSB {
// Relationships
protected $_has_one = array(
'account' => array('foreign_key'=>'id'),
);
}
?>

View File

@ -0,0 +1,32 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* OSB OAuth Model
*
* @package OAuth
* @category Models
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Model_Oauth extends ORM_OSB {
// Relationships
protected $_has_many = array(
'account_oauth' => array('far_key'=>'id'),
);
/**
* Return the object of the OAuth plugin
*/
public function plugin($type='') {
$c = Kohana::classname('Oauth_Plugin_'.$this->name);
if (! $this->name OR ! class_exists($c))
return NULL;
$o = new $c($this);
return $type ? $o->$type : $o;
}
}
?>

View File

@ -0,0 +1,19 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides 3rd party plugin authentication
*
* @package OAuth
* @category Plugins
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
abstract class Oauth_Plugin {
protected $oo; // Our Oauth Object
public function __construct(Model_Oauth $oo) {
$this->oo = $oo;
}
}
?>

View File

@ -0,0 +1,51 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides Facebook Authentication
*
* @package OAuth
* @category Plugins
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Oauth_Plugin_Facebook extends Oauth_Plugin {
public function html() {
// @todo Needs to work with https
Script::factory()
->type('src')
->data('http://connect.facebook.net/en_US/all.js');
Script::factory()
->type('stdin')
->data('
$(document).ready(function(){
window.fbAsyncInit = function() {
// Initialize the Facebook JavaScript SDK
FB.init({
appId: '.$this->oo->app_id.',
xfbml: false,
status: true,
cookie: true,
});
// Check if the current user is logged in and has authorized the app
//FB.getLoginStatus(checkLoginStatus);
}
$(".fb-login").click(function() {
FB.login(checkLoginStatus, {scope:"email"});
// We stop the click, but pick up the href in the javascript
return false;
});
});
');
Script::factory()
->type('file')
->data('media/js/facebook.js');
}
}
?>

View File

@ -0,0 +1,43 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* OAuth Configuration - System Default Configurable Items.
*
* @package OAuth
* @subpackage Facebook
* @category Configuration
* @author Deon George
* @copyright (c) 2010 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
return array(
'cookie' => true,
/* (optional) The URL to redirect the user to once the login/authorization process is complete.
The user will be redirected to the URL on both login success and failure, so you must check the
error parameters in the URL as described in the authentication documentation.
If this property is not specified, the user will be redirected to the current URL
(i.e. the URL of the page where this method was called, typically the current URL in the user's browser). */
'redirect_uri' => url::site(Request::current()->uri(), true),
/* (optional) Next URL to which to redirect the user after logging out (should be an absolute URL). */
'next' => url::site(Request::current()->uri(), true),
/* (optional) The permissions to request from the user. If this property is not specified, basic
permissions will be requested from the user. */
'scope' => 'email',
/* (optional) The display mode in which to render the dialog. The default is page, but can be set to other values
such as popup. */
'display' => 'popup',
/* Fields from users table.
user: http://developers.facebook.com/docs/reference/fql/user/ */
'fields' => 'uid, username, pic, name, email',
/* Where to store session files.
For example: 'native', 'database' */
'session_type' => 'native',
);
?>

View File

@ -0,0 +1,12 @@
// Check the result of the user status and display login button if necessary
function checkLoginStatus(response) {
if (response && response.status == "connected") {
window.parent.location.href = $(".fb-login").attr('href');
} else if (response && response.status === "not_authorized") {
return false;
} else {
return false;
}
}

1462
modules/oauth/vendor/base_facebook.php vendored Normal file

File diff suppressed because it is too large Load Diff

160
modules/oauth/vendor/facebook.php vendored Normal file
View File

@ -0,0 +1,160 @@
<?php
/**
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
require_once "base_facebook.php";
/**
* Extends the BaseFacebook class with the intent of using
* PHP sessions to store user ids and access tokens.
*/
class Facebook extends BaseFacebook
{
const FBSS_COOKIE_NAME = 'fbss';
// We can set this to a high number because the main session
// expiration will trump this.
const FBSS_COOKIE_EXPIRE = 31556926; // 1 year
// Stores the shared session ID if one is set.
protected $sharedSessionID;
/**
* Identical to the parent constructor, except that
* we start a PHP session to store the user ID and
* access token if during the course of execution
* we discover them.
*
* @param Array $config the application configuration. Additionally
* accepts "sharedSession" as a boolean to turn on a secondary
* cookie for environments with a shared session (that is, your app
* shares the domain with other apps).
* @see BaseFacebook::__construct in facebook.php
*/
public function __construct($config) {
if (!session_id()) {
session_start();
}
parent::__construct($config);
if (!empty($config['sharedSession'])) {
$this->initSharedSession();
}
}
protected static $kSupportedKeys =
array('state', 'code', 'access_token', 'user_id');
protected function initSharedSession() {
$cookie_name = $this->getSharedSessionCookieName();
if (isset($_COOKIE[$cookie_name])) {
$data = $this->parseSignedRequest($_COOKIE[$cookie_name]);
if ($data && !empty($data['domain']) &&
self::isAllowedDomain($this->getHttpHost(), $data['domain'])) {
// good case
$this->sharedSessionID = $data['id'];
return;
}
// ignoring potentially unreachable data
}
// evil/corrupt/missing case
$base_domain = $this->getBaseDomain();
$this->sharedSessionID = md5(uniqid(mt_rand(), true));
$cookie_value = $this->makeSignedRequest(
array(
'domain' => $base_domain,
'id' => $this->sharedSessionID,
)
);
$_COOKIE[$cookie_name] = $cookie_value;
if (!headers_sent()) {
$expire = time() + self::FBSS_COOKIE_EXPIRE;
setcookie($cookie_name, $cookie_value, $expire, '/', '.'.$base_domain);
} else {
// @codeCoverageIgnoreStart
self::errorLog(
'Shared session ID cookie could not be set! You must ensure you '.
'create the Facebook instance before headers have been sent. This '.
'will cause authentication issues after the first request.'
);
// @codeCoverageIgnoreEnd
}
}
/**
* Provides the implementations of the inherited abstract
* methods. The implementation uses PHP sessions to maintain
* a store for authorization codes, user ids, CSRF states, and
* access tokens.
*/
protected function setPersistentData($key, $value) {
if (!in_array($key, self::$kSupportedKeys)) {
self::errorLog('Unsupported key passed to setPersistentData.');
return;
}
$session_var_name = $this->constructSessionVariableName($key);
$_SESSION[$session_var_name] = $value;
}
protected function getPersistentData($key, $default = false) {
if (!in_array($key, self::$kSupportedKeys)) {
self::errorLog('Unsupported key passed to getPersistentData.');
return $default;
}
$session_var_name = $this->constructSessionVariableName($key);
return isset($_SESSION[$session_var_name]) ?
$_SESSION[$session_var_name] : $default;
}
protected function clearPersistentData($key) {
if (!in_array($key, self::$kSupportedKeys)) {
self::errorLog('Unsupported key passed to clearPersistentData.');
return;
}
$session_var_name = $this->constructSessionVariableName($key);
unset($_SESSION[$session_var_name]);
}
protected function clearAllPersistentData() {
foreach (self::$kSupportedKeys as $key) {
$this->clearPersistentData($key);
}
if ($this->sharedSessionID) {
$this->deleteSharedSessionCookie();
}
}
protected function deleteSharedSessionCookie() {
$cookie_name = $this->getSharedSessionCookieName();
unset($_COOKIE[$cookie_name]);
$base_domain = $this->getBaseDomain();
setcookie($cookie_name, '', 1, '/', '.'.$base_domain);
}
protected function getSharedSessionCookieName() {
return self::FBSS_COOKIE_NAME . '_' . $this->getAppId();
}
protected function constructSessionVariableName($key) {
$parts = array('fb', $this->getAppId(), $key);
if ($this->sharedSessionID) {
array_unshift($parts, $this->sharedSessionID);
}
return implode('_', $parts);
}
}

View File

@ -0,0 +1,12 @@
<div class="account-container stacked">
<div class="content clearfix">
<h1>Nearly there!</h1>
<p>While we have successfully authenticated you with <?php echo $type; ?>, your account is not linked.</p>
<p>To complete the link, you need to login to OSB just once, then you wont need to do it anymore.</p>
<p>Please press the button below to login and complete the process.</p>
<?php echo HTML::anchor('oauth/link/'.$type,'Link',array('class'=>'button btn btn-warning btn-large')); ?>
</div> <!-- /content -->
</div> <!-- /account-container -->