├── .gitignore ├── .gitmodules ├── .travis.yml ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src └── MultiPass │ ├── AuthHash.php │ ├── Configuration.php │ ├── Error │ ├── CSRFError.php │ └── CallbackError.php │ ├── Strategies │ ├── Dropbox.php │ ├── Facebook.php │ ├── Foursquare.php │ ├── GitHub.php │ ├── Google.php │ ├── Instagram.php │ ├── LinkedIn.php │ ├── OAuth.php │ ├── OAuth2.php │ └── Twitter.php │ └── Strategy.php └── tests ├── MultiPass └── Tests │ ├── AuthHashTest.php │ ├── StrategyTest.php │ └── TestCase.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac files 2 | .DS_Store 3 | *.sw[a-z] 4 | 5 | # Ignore potentially sensitive phpunit file 6 | phpunit.xml 7 | 8 | # Ignore composer generated files 9 | composer.phar 10 | composer.lock 11 | composer-test.lock 12 | vendor/ 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keeguon/MultiPass/894e1315190164d91c940a3d30cd40c4d6553f57/.gitmodules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | 8 | before_script: 9 | - composer install --dev 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiPass [![Build Status](https://secure.travis-ci.org/Keeguon/MultiPass.png)](http://travis-ci.org/Keeguon/MultiPass) 2 | 3 | MultiPass is a kick-ass library to authenticate yourself against third-party APIs using the OAuth 1 or OAuth 2 authentication scheme. It is widely inspired by similar work in other language such as OmniAuth in Ruby or Passport in Node/JS. 4 | 5 | 6 | ## Dependencies 7 | 8 | * PHP 5.4.x or newer 9 | * Official OAuth extension () 10 | * oauth2-php () 11 | 12 | 13 | ## Installation 14 | 15 | ### composer 16 | 17 | To install MultiPass with composer you simply need to create a composer.json in your project root and add: 18 | 19 | ```json 20 | { 21 | "require": { 22 | "multipass/multipass": ">=1.0.0" 23 | } 24 | } 25 | ``` 26 | 27 | Then run 28 | 29 | ```bash 30 | $ wget -nc http://getcomposer.org/composer.phar 31 | $ php composer.phar install 32 | ``` 33 | 34 | You have now MultiPass installed in vendor/multipass/multipass 35 | 36 | And an handy autoload file to include in you project in vendor/.composer/autoload.php 37 | 38 | 39 | ## Testing 40 | 41 | The library is fully tested with PHPUnit for unit tests. To run tests you need PHPUnit which can be installed using the project dependencies as follows: 42 | 43 | ```bash 44 | $ php composer.phar install --dev 45 | ``` 46 | 47 | Then to run the test suites 48 | 49 | ```bash 50 | $ vendor/bin/phpunit 51 | ``` 52 | 53 | 54 | ## License 55 | 56 | Copyright (c) 2013 Félix Bellanger 57 | 58 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 59 | 60 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 63 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multipass/multipass" 3 | , "type": "library" 4 | , "description": "A kick-ass PHP library to connect to third-party applications." 5 | , "keywords": ["oauth", "oauth2", "multipass", "connect"] 6 | , "homepage": "https://github.com/Keeguon/MultiPass" 7 | , "version": "1.4.1" 8 | , "license": "MIT" 9 | , "authors": [ 10 | { 11 | "name": "Félix Bellanger" 12 | , "email": "felix.bellanger@gmail.com" 13 | , "homepage": "http://felixbellanger.com" 14 | } 15 | ] 16 | , "require": { 17 | "php": ">=5.4" 18 | , "keeguon/oauth2-php": "~1.3.5" 19 | } 20 | , "autoload": { 21 | "psr-0": { 22 | "MultiPass\\Tests": "tests/" 23 | , "MultiPass": "src/" 24 | } 25 | } 26 | , "require-dev": { 27 | "symfony/class-loader": "*" 28 | , "phpunit/phpunit": "*" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | tests/MultiPass/ 17 | 18 | 19 | 20 | 21 | 22 | src/MultiPass/ 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/MultiPass/AuthHash.php: -------------------------------------------------------------------------------- 1 | provider = strtolower($provider); 16 | $this->uid = $uid; 17 | if ($info) { 18 | $this->info = $info; 19 | } 20 | } 21 | 22 | public function getName() 23 | { 24 | // return name if available 25 | if (!empty($this->info['name'])) { 26 | return $this->info['name']; 27 | } 28 | 29 | // concatenate first_name and last_name as a fallback 30 | if (!empty($this->info['first_name']) && !empty($this->info['last_name'])) { 31 | return "{$this->info['first_name']} {$this->info['last_name']}"; 32 | } 33 | 34 | // only display first_name or last_name if only that is available 35 | if (!empty($this->info['first_name']) && empty($this->info['last_name'])) { 36 | return $this->info['first_name']; 37 | } 38 | if (empty($this->info['first_name']) && !empty($this->info['last_name'])) { 39 | return $this->info['last_name']; 40 | } 41 | 42 | // return nickname if no name, first or last is available 43 | if (!empty($this->info['nickname'])) { 44 | return $this->info['nickname']; 45 | } 46 | 47 | // return the email if no name, first, last, or nick is available 48 | if (!empty($this->info['email'])) { 49 | return $this->info['email']; 50 | } 51 | 52 | return null; 53 | } 54 | 55 | public function isValid() 56 | { 57 | return ($this->uid && $this->provider && $this->info && $this->getName()); 58 | } 59 | 60 | public function toArray() 61 | { 62 | // put all properties in an array 63 | $array = array( 64 | 'provider' => $this->provider 65 | , 'uid' => $this->uid 66 | , 'info' => $this->info 67 | , 'credentials' => $this->credentials 68 | , 'extra' => $this->extra 69 | ); 70 | 71 | // making sure that the name attribute is set 72 | $array['info']['name'] = !empty($array['info']['name']) ? $array['info']['name'] : $this->getName(); 73 | 74 | return $array; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/MultiPass/Configuration.php: -------------------------------------------------------------------------------- 1 | config[$provider] = $opts; 26 | } 27 | 28 | public function registerConfigs($configs = array()) 29 | { 30 | foreach ($configs as $provider => $opts) { 31 | $this->registerConfig($provider, $opts); 32 | } 33 | } 34 | 35 | public function register() 36 | { 37 | foreach ($this->config as $provider => $opts) { 38 | $strategy = "\MultiPass\Strategies\\".ucfirst($provider); 39 | $this->strategies[$provider] = new $strategy($opts); 40 | } 41 | } 42 | 43 | public function getStrategy($provider) 44 | { 45 | return $this->strategies[$provider]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/MultiPass/Error/CSRFError.php: -------------------------------------------------------------------------------- 1 | error = $error; 16 | $this->error_reason = $error_reason; 17 | $this->error_uri = $error_uri; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/MultiPass/Strategies/Dropbox.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 13 | 'client_options' => array( 14 | 'site' => 'https://api.dropbox.com' 15 | , 'token_url' => '/1/oauth2/token' 16 | , 'authorize_url' => 'https://www.dropbox.com/1/oauth2/authorize' 17 | ) 18 | ), $opts); 19 | 20 | parent::__construct($this->options); 21 | } 22 | 23 | public function authorizeParams() 24 | { 25 | $params = parent::authorizeParams(); 26 | if (isset($_REQUEST['state']) && $_REQUEST['state'] !== '') { 27 | $params['state'] = $_REQUEST['state']; 28 | } 29 | return $params; 30 | } 31 | 32 | public function uid($rawInfo = null) 33 | { 34 | $rawInfo = $rawInfo ?: $this->rawInfo(); 35 | return $rawInfo['uid']; 36 | } 37 | 38 | public function info($rawInfo = null) 39 | { 40 | $rawInfo = $rawInfo ?: $this->rawInfo(); 41 | 42 | return array( 43 | 'referral_link' => $rawInfo['referral_link'], 44 | 'display_name'=> $rawInfo['display_name'], 45 | 'country'=> $rawInfo['country'], 46 | 'quota_info' => $rawInfo['quota_info'], 47 | ); 48 | } 49 | 50 | protected function rawInfo() 51 | { 52 | $response = $this->accessToken->get('/1/account/info', array('parse' => 'json')); 53 | $parsedResponse = $response->parse(); 54 | return $parsedResponse; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/MultiPass/Strategies/Facebook.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 15 | 'client_options' => array( 16 | 'site' => 'https://graph.facebook.com/v2.1' 17 | , 'token_url' => '/oauth/access_token' 18 | , 'client_auth' => 'query' 19 | ) 20 | , 'token_params' => array( 21 | 'parse' => 'query' 22 | ) 23 | , 'token_options' => array( 24 | 'header_format' => 'OAuth %s' 25 | , 'param_name' => 'access_token' 26 | ) 27 | ), $opts); 28 | 29 | parent::__construct($this->options); 30 | } 31 | 32 | public function uid($rawInfo = null) 33 | { 34 | $rawInfo = $rawInfo ?: $this->rawInfo(); 35 | 36 | return $rawInfo['id']; 37 | } 38 | 39 | public function info($rawInfo = null) 40 | { 41 | $rawInfo = $rawInfo ?: $this->rawInfo(); 42 | 43 | return array( 44 | 'nickname' => isset($rawInfo['username']) ? $rawInfo['username'] : $rawInfo['id'] 45 | , 'email' => $rawInfo['email'] 46 | , 'name' => $rawInfo['name'] 47 | , 'first_name' => $rawInfo['first_name'] 48 | , 'last_name' => $rawInfo['last_name'] 49 | , 'image' => "http://graph.facebook.com/{$rawInfo['id']}/picture?type=square" 50 | , 'description' => isset($rawInfo['bio']) ? $rawInfo['bio'] : null 51 | , 'urls' => array( 52 | 'Facebook' => $rawInfo['link'] 53 | , 'Website' => isset($rawInfo['website']) ? $rawInfo['website'] : null 54 | ) 55 | , 'location' => isset($rawInfo['location']) ? $rawInfo['location']['name'] : null 56 | ); 57 | } 58 | 59 | public function authorizeParams() 60 | { 61 | $params = parent::authorizeParams(); 62 | if (isset($_REQUEST['display']) && $_REQUEST['display'] !== '') { 63 | $params['display'] = $_REQUEST['display']; 64 | } 65 | if (isset($_REQUEST['state']) && $_REQUEST['state'] !== '') { 66 | $params['state'] = $_REQUEST['state']; 67 | } 68 | $params['scope'] = isset($params['scope']) ? $params['scope'] : self::DEFAULT_SCOPE; 69 | 70 | return $params; 71 | } 72 | 73 | protected function rawInfo() 74 | { 75 | try { 76 | $response = $this->accessToken->get('/me', array('parse' => 'json')); 77 | return $response->parse(); 78 | } catch (\Exception $e) { 79 | print_r($e); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/MultiPass/Strategies/Foursquare.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 13 | 'client_options' => array( 14 | 'site' => 'https://foursquare.com' 15 | , 'token_url' => '/oauth2/access_token' 16 | , 'authorize_url' => '/oauth2/authenticate' 17 | , 'token_method' => 'GET' 18 | ) 19 | , 'token_params' => array( 20 | 'parse' => 'json' 21 | ) 22 | , 'token_options' => array( 23 | 'mode' => 'query' 24 | , 'param_name' => 'oauth_token' 25 | ) 26 | ), $opts); 27 | 28 | parent::__construct($this->options); 29 | } 30 | 31 | public function uid($rawInfo = null) 32 | { 33 | $rawInfo = $rawInfo ?: $this->rawInfo(); 34 | 35 | return $rawInfo['id']; 36 | } 37 | 38 | public function info($rawInfo = null) 39 | { 40 | $rawInfo = $rawInfo ?: $this->rawInfo(); 41 | 42 | return array( 43 | 'first_name' => $rawInfo['firstName'] 44 | , 'last_name' => $rawInfo['lastName'] 45 | , 'image' => $rawInfo['photo'] 46 | ); 47 | } 48 | 49 | protected function rawInfo() 50 | { 51 | try { 52 | $response = $this->accessToken->get('https://api.foursquare.com/v2/users/self', array('query' => array('v' => strftime('%Y%m%d')), 'parse' => 'json')); 53 | $parsedResponse = $response->parse(); 54 | return $parsedResponse['response']['user']; 55 | } catch (\Exception $e) { 56 | print_r($e); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/MultiPass/Strategies/GitHub.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 13 | 'client_options' => array( 14 | 'site' => 'https://api.github.com' 15 | , 'authorize_url' => 'https://github.com/login/oauth/authorize' 16 | , 'token_url' => 'https://github.com/login/oauth/access_token' 17 | , 'client_auth' => 'query' 18 | ) 19 | , 'token_params' => array( 20 | 'parse' => 'query' 21 | ) 22 | , 'token_options' => array( 23 | 'mode' => 'query' 24 | , 'param_name' => 'access_token' 25 | ) 26 | ), $opts); 27 | 28 | parent::__construct($this->options); 29 | } 30 | 31 | public function uid($rawInfo = null) 32 | { 33 | $rawInfo = $rawInfo ?: $this->rawInfo(); 34 | 35 | return $rawInfo['id']; 36 | } 37 | 38 | public function info($rawInfo = null) 39 | { 40 | $rawInfo = $rawInfo ?: $this->rawInfo(); 41 | 42 | return array( 43 | 'nickname' => $rawInfo['login'] 44 | , 'email' => $rawInfo['email'] 45 | , 'name' => $rawInfo['name'] 46 | , 'image' => $rawInfo['avatar_url'] 47 | , 'description' => $rawInfo['bio'] 48 | , 'urls' => array( 49 | 'GitHub' => $rawInfo['html_url'] 50 | , 'Blog' => $rawInfo['blog'] 51 | ) 52 | ); 53 | } 54 | 55 | protected function rawInfo() 56 | { 57 | try { 58 | $response = $this->accessToken->get('/user', array('parse' => 'json')); 59 | return $response->parse(); 60 | } catch (Exception $e) { 61 | print_r($e); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/MultiPass/Strategies/Google.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Configure with: 10 | * google: 11 | * client_id: "MYID.apps.googleusercontent.com" 12 | * client_secret: "mysecret" 13 | */ 14 | 15 | class Google extends \MultiPass\Strategies\OAuth2 16 | { 17 | protected $name = 'google'; 18 | 19 | public function __construct($opts = array()) 20 | { 21 | // Default options 22 | $this->options = array_replace_recursive(array( 23 | 'client_options' => array( 24 | 'site' => 'https://accounts.google.com/o/oauth2' 25 | , 'token_url' => '/token' 26 | , 'authorize_url' => '/auth' 27 | , 'client_auth' => 'body' 28 | ) 29 | , 'authorize_params' => array('scope' => 'openid profile email') 30 | ), $opts); 31 | 32 | parent::__construct($this->options); 33 | } 34 | 35 | public function uid($rawInfo = null) 36 | { 37 | $rawInfo = $rawInfo ?: $this->rawInfo(); 38 | 39 | return (array_key_exists('id', $rawInfo) ? $rawInfo['id'] : $rawInfo['sub']); 40 | } 41 | 42 | public function info($rawInfo = null) 43 | { 44 | $rawInfo = $rawInfo ?: $this->rawInfo(); 45 | 46 | return array( 47 | 'email' => $rawInfo['email'] 48 | , 'name' => (empty($rawInfo['name']) ? $rawInfo['given_name'].' '.$rawInfo['family_name'] : $rawInfo['name']) 49 | , 'first_name' => $rawInfo['given_name'] 50 | , 'last_name' => $rawInfo['family_name'] 51 | , 'image' => (array_key_exists('picture', $rawInfo) ? $rawInfo['picture'] : null) 52 | , 'gender' => (array_key_exists('gender', $rawInfo) ? $rawInfo['gender'] : null) 53 | ); 54 | } 55 | 56 | protected function rawInfo() 57 | { 58 | try { 59 | $response = $this->accessToken->get('https://www.googleapis.com/oauth2/v3/userinfo', array('parse' => 'json')); 60 | $parsedResponse = $response->parse(); 61 | return $parsedResponse; 62 | } catch (\Exception $e) { 63 | print_r($e); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/MultiPass/Strategies/Instagram.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 15 | 'client_options' => array( 16 | 'site' => 'https://api.instagram.com' 17 | , 'token_url' => '/oauth/access_token' 18 | , 'client_auth' => 'body' 19 | ) 20 | , 'token_params' => array( 21 | 'parse' => 'json' 22 | ) 23 | , 'token_options' => array( 24 | 'mode' => 'query' 25 | , 'param_name' => 'access_token' 26 | ) 27 | ), $opts); 28 | 29 | parent::__construct($this->options); 30 | } 31 | 32 | public function info($rawInfo = null) 33 | { 34 | $rawInfo = $rawInfo ?: $this->rawInfo(); 35 | 36 | return array( 37 | 'nickname' => $rawInfo['username'] 38 | , 'name' => $rawInfo['full_name'] 39 | , 'image' => $rawInfo['profile_picture'] 40 | , 'description' => $rawInfo['bio'] 41 | , 'urls' => array( 42 | 'Website' => $rawInfo['website'] 43 | ) 44 | ); 45 | } 46 | 47 | public function authorizeParams() 48 | { 49 | $params = parent::authorizeParams(); 50 | $params['scope'] = isset($params['scope']) ? $params['scope'] : self::DEFAULT_SCOPE; 51 | 52 | return $params; 53 | } 54 | 55 | protected function rawInfo() 56 | { 57 | try { 58 | $response = $this->accessToken->get('/v1/users/self', array('parse' => 'json')); 59 | $parsedResponse = $response->parse(); 60 | return $parsedResponse['data']; 61 | } catch (\Exception $e) { 62 | print_r($e); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/MultiPass/Strategies/LinkedIn.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 13 | 'client_options' => array( 14 | 'site' => 'https://www.linkedin.com/uas/oauth2' 15 | , 'token_url' => '/accessToken' 16 | , 'authorize_url' => '/authorization' 17 | , 'fields' => 'id,first-name,last-name,picture-url,site-standard-profile-request' 18 | ), 19 | 'token_params' => array( 20 | 'parse' => 'json', 21 | ), 22 | 'token_options' => array( 23 | 'mode' => 'query' 24 | , 'param_name' => 'oauth2_access_token' 25 | ) 26 | ), $opts); 27 | 28 | parent::__construct($this->options); 29 | } 30 | 31 | public function authorizeParams() 32 | { 33 | $params = parent::authorizeParams(); 34 | if (isset($_REQUEST['state']) && $_REQUEST['state'] !== '') { 35 | $params['state'] = $_REQUEST['state']; 36 | } else { 37 | // State is required in LinkedIn 38 | $params['state'] = 39 | $_SESSION['oauth'][$this->name]['state'] = 40 | uniqid($this->name); 41 | } 42 | return $params; 43 | } 44 | 45 | public function uid($rawInfo = null) 46 | { 47 | $rawInfo = $rawInfo ?: $this->rawInfo(); 48 | //$matches = NULL; 49 | // Get number ID from siteStandardProfile 50 | //if (isset($rawInfo['siteStandardProfileRequest']['url'])) { 51 | // preg_match('/id=(\d+)/', $rawInfo['siteStandardProfileRequest']['url'], $matches); 52 | //} 53 | // 54 | //if ($matches) { 55 | // return $matches[1]; 56 | //} else 57 | // // This ID is changing, if you change client_id 58 | return $rawInfo['id']; 59 | } 60 | 61 | public function info($rawInfo = null) 62 | { 63 | $rawInfo = $rawInfo ?: $this->rawInfo(); 64 | $firstName = isset($rawInfo['firstName']) ? $rawInfo['firstName'] : ''; 65 | $lastName = isset($rawInfo['lastName']) ? $rawInfo['lastName'] : ''; 66 | return array( 67 | 'id' => isset($rawInfo['id']) ? $rawInfo['id'] : NULL, 68 | 'first_name' => $firstName, 69 | 'last_name' => $lastName, 70 | 'name' => empty($rawInfo['name']) 71 | ? $firstName . ' ' . $lastName 72 | : $rawInfo['name'], 73 | 'email' => isset($rawInfo['emailAddress']) ? $rawInfo['emailAddress'] : NULL, 74 | 'image' => isset($rawInfo['pictureUrl']) ? $rawInfo['pictureUrl'] : NULL, 75 | ); 76 | } 77 | 78 | public function callbackPhase() { 79 | if (!isset($_GET['state']) || $_GET['state'] !== $_SESSION['oauth'][$this->name]['state']) { 80 | throw new \MultiPass\Error\CSRFError('CSRF protection', 'Returned "state" value is not correct. Possible CSRF'); 81 | } 82 | return parent::callbackPhase(); 83 | } 84 | 85 | protected function rawInfo() 86 | { 87 | $response = $this->accessToken->get('https://api.linkedin.com/v1/people/~:(' . $this->options['client_options']['fields'] . ')', array( 88 | 'parse' => 'json', 89 | 'params' => array( 'format' => 'json' ) 90 | )); 91 | return $response->parse(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/MultiPass/Strategies/OAuth.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 18 | 'client_options' => array( 19 | 'auth_type' => OAUTH_AUTH_TYPE_AUTHORIZATION 20 | , 'signature_method' => OAUTH_SIG_METHOD_HMACSHA1 21 | ) 22 | ), $this->options); 23 | 24 | // Instanciate client 25 | $this->client = new \OAuth($this->options['client_id'], $this->options['client_secret'], $this->options['client_options']['signature_method'], $this->options['client_options']['auth_type']); 26 | } 27 | 28 | public function getAuthType() 29 | { 30 | return 'oauth2'; 31 | } 32 | 33 | public function getClient() 34 | { 35 | return $this->client; 36 | } 37 | 38 | public function uid() {} 39 | 40 | public function info() {} 41 | 42 | public function credentials() 43 | { 44 | return array( 45 | 'token' => $_SESSION['oauth'][$this->name]['oauth_token'] 46 | , 'secret' => $_SESSION['oauth'][$this->name]['oauth_token_secret'] 47 | ); 48 | } 49 | 50 | public function extra($rawInfo = null) 51 | { 52 | $rawInfo = $rawInfo ?: $this->rawInfo(); 53 | 54 | return array('raw_info' => $rawInfo); 55 | } 56 | 57 | public function requestPhase() 58 | { 59 | // Fetch request token 60 | $requestToken = $this->client->getRequestToken($this->requestTokenUrl(), $this->getCallbackUrl() ?: 'oob'); 61 | 62 | // Throw exception if the callback isn't confirmed 63 | if (!in_array($requestToken['oauth_callback_confirmed'], array(true, "true"))) { 64 | throw new \Exception("There was an error regarding the callback confirmation"); 65 | } 66 | 67 | // Store the OAuth Token and the OAuth Token Secret in the session 68 | $_SESSION['oauth'][$this->name] = array( 69 | 'oauth_token' => $requestToken['oauth_token'] 70 | , 'oauth_token_secret' => $requestToken['oauth_token_secret'] 71 | ); 72 | 73 | // Redirect the user to the Provider Authorize page 74 | header('Location: '.$this->authorizeUrl(array('oauth_token' => $requestToken['oauth_token']))); 75 | exit; 76 | } 77 | 78 | public function callbackPhase() 79 | { 80 | // Fetch access token 81 | $this->client->setToken($_GET['oauth_token'], $_SESSION['oauth'][$this->name]['oauth_token_secret']); 82 | $accessToken = $this->client->getAccessToken($this->accessTokenUrl(array('oauth_token' => $_GET['oauth_token']))); 83 | 84 | // Store access token informations 85 | $_SESSION['oauth'][$this->name] = array( 86 | 'oauth_token' => $accessToken['oauth_token'] 87 | , 'oauth_token_secret' => $accessToken['oauth_token_secret'] 88 | ); 89 | 90 | // Set the client token w/ the last token informations 91 | $this->client->setToken($_SESSION['oauth'][$this->name]['oauth_token'], $_SESSION['oauth'][$this->name]['oauth_token_secret']); 92 | 93 | return parent::callbackPhase(); 94 | } 95 | 96 | protected function accessTokenUrl($params = array()) 97 | { 98 | $accessTokenUrl = isset($this->options['client_options']['access_token_url']) ? $this->options['client_options']['access_token_url'] : $this->options['client_options']['site'].$this->options['client_options']['access_token_path']; 99 | 100 | return $params ? $accessTokenUrl.'?'.http_build_query($params) : $accessTokenUrl; 101 | } 102 | 103 | protected function authorizeUrl($params = array()) 104 | { 105 | $authorizeUrl = isset($this->options['client_options']['authorize_url']) ? $this->options['client_options']['authorize_url'] : $this->options['client_options']['site'].$this->options['client_options']['authorize_path']; 106 | 107 | return $params ? $authorizeUrl.'?'.http_build_query($params) : $authorizeUrl; 108 | } 109 | 110 | protected function requestTokenUrl($params = array()) 111 | { 112 | $requestTokenUrl = isset($this->options['client_options']['request_token_url']) ? $this->options['client_options']['request_token_url'] : $this->options['client_options']['site'].$this->options['client_options']['request_token_path']; 113 | 114 | return $params ? $requestTokenUrl.'?'.http_build_query($params) : $requestTokenUrl; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/MultiPass/Strategies/OAuth2.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 19 | 'client_id' => null 20 | , 'client_secret' => array() 21 | , 'client_options' => array() 22 | , 'authorize_params' => array() 23 | , 'authorize_options' => array() 24 | , 'token_params' => array() 25 | , 'token_options' => array() 26 | ), $this->options); 27 | 28 | // Instanciate client 29 | $this->client = new \OAuth2\Client($this->options['client_id'], $this->options['client_secret'], $this->options['client_options']); 30 | } 31 | 32 | public function getAuthType() 33 | { 34 | return 'oauth2'; 35 | } 36 | 37 | public function getClient() 38 | { 39 | return $this->client; 40 | } 41 | 42 | public function getCallbackUrl() 43 | { 44 | return $this->getFullHost().$this->getCallbackPath(); 45 | } 46 | 47 | public function uid() {} 48 | 49 | public function info() {} 50 | 51 | public function credentials() 52 | { 53 | $hash = array('token' => $this->accessToken->getToken()); 54 | if ($this->accessToken->expires() && $this->accessToken->getRefreshToken()) { 55 | $hash['refresh_token'] = $this->accessToken->getRefreshToken(); 56 | } 57 | if ($this->accessToken->expires()) { 58 | $hash['expires_in'] = $this->accessToken->getExpiresIn(); 59 | } 60 | return $hash; 61 | } 62 | 63 | public function extra($rawInfo = null) 64 | { 65 | $rawInfo = $rawInfo ?: $this->rawInfo(); 66 | 67 | return array('raw_info' => $rawInfo); 68 | } 69 | 70 | public function requestPhase() 71 | { 72 | header('Location: '.$this->client->authCode()->authorizeUrl(array_merge(array('redirect_uri' => $this->getCallbackUrl()), $this->authorizeParams()))); 73 | exit; 74 | } 75 | 76 | public function authorizeParams() 77 | { 78 | return array_merge($this->options['authorize_params'], $this->options['authorize_options']); 79 | } 80 | 81 | public function tokenParams() 82 | { 83 | return array_merge($this->options['token_params'], $this->options['token_options']); 84 | } 85 | 86 | public function callbackPhase() 87 | { 88 | if (isset($_GET['error'])) { 89 | throw new \MultiPass\Error\CallbackError($_GET['error'], isset($_GET['error_description']) ? $_GET['error_description'] : null, isset($_GET['error_uri']) ? $_GET['error_uri'] : null); 90 | } 91 | 92 | $this->accessToken = $this->buildAccessToken(); 93 | if ($this->accessToken->isExpired()) { 94 | $this->accessToken = $this->accessToken->refresh(); 95 | } 96 | 97 | return parent::callbackPhase(); 98 | } 99 | 100 | protected function buildAccessToken() 101 | { 102 | $verifier = $_GET['code']; 103 | return $this->client->authCode()->getToken($verifier, array_merge(array('redirect_uri' => $this->getCallbackUrl()), $this->options['token_params']), $this->options['token_options']); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/MultiPass/Strategies/Twitter.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 13 | 'client_options' => array( 14 | 'access_token_path' => '/oauth/access_token' 15 | , 'authorize_path' => '/oauth/authorize' 16 | , 'request_token_path' => '/oauth/request_token' 17 | , 'site' => 'https://api.twitter.com' 18 | ) 19 | ), $opts); 20 | 21 | parent::__construct($this->options); 22 | } 23 | 24 | public function uid($rawInfo = null) { 25 | $rawInfo = $rawInfo ?: $this->rawInfo(); 26 | 27 | return $rawInfo['id']; 28 | } 29 | 30 | public function info($rawInfo = null) 31 | { 32 | $rawInfo = $rawInfo ?: $this->rawInfo(); 33 | 34 | return array( 35 | 'nickname' => $rawInfo['screen_name'] 36 | , 'name' => $rawInfo['name'] 37 | , 'location' => $rawInfo['location'] 38 | , 'image' => $rawInfo['profile_image_url'] 39 | , 'description' => $rawInfo['description'] 40 | , 'urls' => array( 41 | 'Twitter' => 'http://twitter.com/'.$rawInfo['screen_name'] 42 | , 'Website' => isset($rawInfo['url']) ? $rawInfo['url'] : null 43 | ) 44 | ); 45 | } 46 | 47 | protected function rawInfo() 48 | { 49 | try { 50 | $this->client->fetch($this->options['client_options']['site'].'/1.1/account/verify_credentials.json'); 51 | return json_decode($this->client->getLastResponse(), true); 52 | } catch (\Exception $e) { 53 | print_r($e); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/MultiPass/Strategy.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 15 | 'path_prefix' => '/auth' 16 | , 'skip_info' => false 17 | ), $opts); 18 | } 19 | 20 | public function configure($opts = array()) 21 | { 22 | $this->options = array_replace_recursive($opts, $this->options); 23 | } 24 | 25 | abstract public function getAuthType(); 26 | 27 | abstract public function requestPhase(); 28 | 29 | abstract public function uid(); 30 | 31 | abstract public function info(); 32 | 33 | abstract public function credentials(); 34 | 35 | abstract public function extra(); 36 | 37 | public function authHash() { 38 | $hash = new \MultiPass\AuthHash($this->name, $this->uid()); 39 | if (false === $this->options['skip_info']) { 40 | $hash->info = $this->info(); 41 | } 42 | $hash->credentials = $this->credentials() ?: null; 43 | $hash->extra = $this->extra() ?: null; 44 | 45 | return $hash; 46 | } 47 | 48 | public function callbackPhase() { 49 | return $this->authHash(); 50 | } 51 | 52 | public function getPathPrefix() 53 | { 54 | return array_key_exists('path_prefix', $this->options) ? $this->options['path_prefix'] : '/auth'; 55 | } 56 | 57 | public function getRequestPath() 58 | { 59 | return array_key_exists('request_path', $this->options) ? $this->options['request_path'] : $this->getPathPrefix().'/'.$this->name; 60 | } 61 | 62 | public function getCallbackPath() 63 | { 64 | return array_key_exists('callback_path', $this->options) ? $this->options['callback_path'] : $this->getPathPrefix().'/'.$this->name.'/callback'; 65 | } 66 | 67 | public function getCurrentPath() 68 | { 69 | $parsedUrl = parse_url($_SERVER['REQUEST_URI']); 70 | 71 | return $parsedUrl['path']; 72 | } 73 | 74 | public function getQueryString() 75 | { 76 | $parsedUrl = parse_url($_SERVER['REQUEST_URI']); 77 | 78 | return isset($parsedUrl['query']) ? $parsedUrl['query'] : null; 79 | } 80 | 81 | public function getFullHost() 82 | { 83 | return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https://'.$_SERVER['HTTP_HOST'] : 'http://'.$_SERVER['HTTP_HOST']; 84 | } 85 | 86 | public function getCallbackUrl() 87 | { 88 | return $this->getQueryString() ? $this->getFullHost().$this->getCallbackPath().'?'.$this->getQueryString() : $this->getFullHost().$this->getCallbackPath(); 89 | } 90 | 91 | public function getName() 92 | { 93 | return $this->name; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/MultiPass/Tests/AuthHashTest.php: -------------------------------------------------------------------------------- 1 | authHash = new \MultiPass\AuthHash('example', '123', array('name' => 'Steven')); 18 | } 19 | 20 | protected function tearDown() 21 | { 22 | unset($this->authHash); 23 | } 24 | 25 | /** 26 | * @covers MultiPass\AuthHash::isValid() 27 | */ 28 | public function testIsValid() 29 | { 30 | // should be valid with the right parameters 31 | $this->assertTrue($this->authHash->isValid()); 32 | 33 | // should require a uid 34 | $this->authHash->uid = null; 35 | $this->assertFalse($this->authHash->isValid()); 36 | $this->authHash->uid = '123'; 37 | 38 | // should require a provider 39 | $this->authHash->provider = null; 40 | $this->assertFalse($this->authHash->isValid()); 41 | $this->authHash->provider = 'example'; 42 | 43 | // should require a name in the user info hash 44 | $this->authHash->info['name'] = null; 45 | $this->assertFalse($this->authHash->isValid()); 46 | $this->authHash->info['name'] = 'Steve'; 47 | } 48 | 49 | /** 50 | * @covers MultiPass\AuthHash::getName() 51 | */ 52 | public function testName() 53 | { 54 | // redefine info hash 55 | $this->authHash->info = array( 56 | 'name' => 'Phillip J. Fry' 57 | , 'first_name' => 'Phillip' 58 | , 'last_name' => 'Fry' 59 | , 'nickname' => 'meatbag' 60 | , 'email' => 'fry@planetexpress.com' 61 | ); 62 | 63 | // should default to the name key 64 | $this->assertEquals('Phillip J. Fry', $this->authHash->getName()); 65 | 66 | // should fall back to go to first_name last_name concatenation 67 | $this->authHash->info['name'] = null; 68 | $this->assertEquals('Phillip Fry', $this->authHash->getName()); 69 | 70 | // should display only a first or last name if only that is available 71 | $this->authHash->info['first_name'] = null; 72 | $this->assertEquals('Fry', $this->authHash->getName()); 73 | 74 | // should display the nickname if no name, first, or last is available 75 | $this->authHash->info['last_name'] = null; 76 | $this->assertEquals('meatbag', $this->authHash->getName()); 77 | 78 | // should display the email if no name, first, last, or nick is available 79 | $this->authHash->info['nickname'] = null; 80 | $this->assertEquals('fry@planetexpress.com', $this->authHash->getName()); 81 | } 82 | 83 | /** 84 | * @covers MultiPass\AuthHash::toArray() 85 | */ 86 | public function testToArray() 87 | { 88 | $this->authHash = new \MultiPass\AuthHash('test', '123', array('name' => 'Bob Example')); 89 | 90 | // should be a plain old array 91 | $this->assertInternalType('array', $this->authHash->toArray()); 92 | 93 | // should have string keys 94 | $this->assertArrayHasKey('uid', $this->authHash->toArray()); 95 | 96 | // info hash should also be a plain old array 97 | $this->authHash->info = array('first_name' => 'Bob', 'last_name' => 'Example'); 98 | $authHashArray = $this->authHash->toArray(); 99 | $this->assertInternalType('array', $authHashArray['info']); 100 | 101 | // should supply the calculated name in the converted hash 102 | $this->authHash->info = array('first_name' => 'Bob', 'last_name' => 'Examplar'); 103 | $authHashArray = $this->authHash->toArray(); 104 | $this->assertEquals('Bob Examplar', $authHashArray['info']['name']); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/MultiPass/Tests/StrategyTest.php: -------------------------------------------------------------------------------- 1 | options = array_replace_recursive(array( 15 | 'wakka' => 'doo' 16 | ), $this->options); 17 | } 18 | 19 | public function getAuthType() { 20 | return 'authExample'; 21 | } 22 | 23 | public function requestPhase() {} 24 | 25 | public function uid() {} 26 | 27 | public function info() {} 28 | 29 | public function credentials() {} 30 | 31 | public function extra() {} 32 | } 33 | 34 | class StrategyTest extends \MultiPass\Tests\TestCase 35 | { 36 | /** 37 | * @var MultiPass\Strategy 38 | */ 39 | protected $strategy; 40 | 41 | /** 42 | * Set up fixtures 43 | */ 44 | protected function setUp() 45 | { 46 | $this->strategy = $this->getMock('MultiPass\Tests\ExampleStrategy', array('authHash'), array(array('foo' => 'bar'))); 47 | 48 | $this->strategy->expects($this->any()) 49 | ->method('authHash') 50 | ->will($this->returnValue('AUTH HASH')); 51 | } 52 | 53 | protected function tearDown() 54 | { 55 | unset($this->strategy); 56 | } 57 | 58 | /** 59 | * @cover MultiPass\Strategy::__construct() 60 | */ 61 | public function testConstructorBuildsStrategy() 62 | { 63 | // should be a subclass of MultiPass\Strategy 64 | $this->assertTrue(is_subclass_of($this->strategy, '\MultiPass\Strategy')); 65 | 66 | // options should be inherited from parent 67 | $this->assertEquals('bar', $this->strategy->options['foo']); 68 | 69 | // contructor should also set default options for the object 70 | $this->assertEquals('doo', $this->strategy->options['wakka']); 71 | } 72 | 73 | /** 74 | * @cover MultiPass\Strategy::configure() 75 | */ 76 | public function testConfigure() 77 | { 78 | // should take a hash and deep merge it 79 | $this->strategy->configure(array('abc' => array('def' => 123))); 80 | $this->strategy->configure(array('abc' => array('ghi' => 456))); 81 | $this->assertArrayEquals(array('abc' => array('def' => 123, 'ghi' => 456)), $this->strategy->options['abc']); 82 | } 83 | 84 | /** 85 | * @cover MultiPass\Strategy::getAuthType() 86 | */ 87 | public function testAuthType() 88 | { 89 | // should return strategy auth type 90 | $this->assertEquals('authExample', $this->strategy->getAuthType()); 91 | } 92 | 93 | /** 94 | * @cover MultiPass\Strategy::callbackPhase() 95 | */ 96 | public function testCallbackPhase() 97 | { 98 | // should set the auth hash 99 | $this->assertEquals('AUTH HASH', $this->strategy->callbackPhase()); 100 | } 101 | 102 | /** 103 | * @cover MultiPass\Strategy::getPathPrefix() 104 | * @cover MultiPass\Strategy::getRequestPath() 105 | * @cover MultiPass\Strategy::getCallbackPath() 106 | */ 107 | public function testPathMethods() 108 | { 109 | // path_prefix should default to '/auth' 110 | $this->assertEquals('/auth', $this->strategy->getPathPrefix()); 111 | 112 | // custom path_prefix 113 | $this->strategy->options['path_prefix'] = '/connect'; 114 | $this->assertEquals('/connect', $this->strategy->getPathPrefix()); 115 | 116 | // request_path should default to "{$path_prefix}/{$name}" 117 | $this->assertEquals('/connect/example', $this->strategy->getRequestPath()); 118 | 119 | // custom request_path 120 | $this->strategy->options['request_path'] = "/{$this->strategy->getName()}/request"; 121 | $this->assertEquals('/example/request', $this->strategy->getRequestPath()); 122 | 123 | // callback_path should default to "{$path_prefix}/{$name}/callback" 124 | $this->assertEquals('/connect/example/callback', $this->strategy->getCallbackPath()); 125 | 126 | // custom callback_path 127 | $this->strategy->options['request_path'] = "/{$this->strategy->getName()}/callback"; 128 | $this->assertEquals('/example/callback', $this->strategy->getRequestPath()); 129 | } 130 | 131 | /** 132 | * @cover MultiPass\Strategy::getName() 133 | */ 134 | public function testGetName() 135 | { 136 | $this->assertEquals('example', $this->strategy->getName()); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/MultiPass/Tests/TestCase.php: -------------------------------------------------------------------------------- 1 | deepKsort($expected); 17 | $this->deepKsort($actual); 18 | 19 | return $expected === $actual; 20 | } 21 | 22 | /** 23 | * Deeply sort array by keys 24 | * 25 | * @param $array The array we're going to ksort 26 | */ 27 | private function deepKsort(&$array) 28 | { 29 | if (!is_array($array)) { 30 | return false; 31 | } 32 | 33 | ksort($array); 34 | foreach ($array as $k => $v) { 35 | $this->deepKsort($array[$k]); 36 | } 37 | 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |