From e86586a29712fe8edf40e828798341a54c6631dd Mon Sep 17 00:00:00 2001 From: Olav Morken <olav.morken@uninett.no> Date: Tue, 8 Feb 2011 13:51:32 +0000 Subject: [PATCH] New authentication source: authlinkedin Thanks to Brook Schofield for implementing this. git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2736 44740490-163a-0410-bde0-09ae8108e29a --- attributemap/linkedin2name.php | 19 +++ config-templates/authsources.php | 11 ++ modules/authlinkedin/default-disable | 3 + modules/authlinkedin/docs/oauthlinkedin.txt | 26 ++++ .../authlinkedin/lib/Auth/Source/LinkedIn.php | 122 ++++++++++++++++++ modules/authlinkedin/www/linkback.php | 34 +++++ 6 files changed, 215 insertions(+) create mode 100644 attributemap/linkedin2name.php create mode 100644 modules/authlinkedin/default-disable create mode 100644 modules/authlinkedin/docs/oauthlinkedin.txt create mode 100644 modules/authlinkedin/lib/Auth/Source/LinkedIn.php create mode 100644 modules/authlinkedin/www/linkback.php diff --git a/attributemap/linkedin2name.php b/attributemap/linkedin2name.php new file mode 100644 index 000000000..0d3a43303 --- /dev/null +++ b/attributemap/linkedin2name.php @@ -0,0 +1,19 @@ +<?php +$attributemap = array( + + // See http://developer.linkedin.com/docs/DOC-1061 for LinkedIn Profile fields. + // NB: JSON response requires the conversion of field names from hyphened to camelCase. + // For instance, first-name becomes firstName. + + // Generated LinkedIn Attributes + 'linkedin_user' => 'eduPersonPrincipalName', // id @ linkedin.com + 'linkedin_targetedID' => 'eduPersonTargetedID', // http://linkedin.com!id + + // Attributes Returned by LinkedIn + 'linkedin.firstName' => 'givenName', + 'linkedin.lastName' => 'sn', + 'linkedin.id' => 'uid', // alpha + mixed case user id + //'linkedin.pictureUrl' => 'jpegPhoto', // URL not image data + 'linkedin.headline' => 'title', + 'linkedin.summary' => 'description', +); diff --git a/config-templates/authsources.php b/config-templates/authsources.php index 8c870901d..89a86a4de 100644 --- a/config-templates/authsources.php +++ b/config-templates/authsources.php @@ -134,6 +134,17 @@ $config = array( ), */ + /* + // LinkedIn OAuth Authentication API. + // Register your application to get an API key here: + // https://www.linkedin.com/secure/developer + 'linkedin' => array( + 'authlinkedin:LinkedIn', + 'key' => 'xxxxxxxxxxxxxxxx', + 'secret' => 'xxxxxxxxxxxxxxxx', + ), + */ + /* // Twitter OAuth Authentication API. // Register your application to get an API key here: diff --git a/modules/authlinkedin/default-disable b/modules/authlinkedin/default-disable new file mode 100644 index 000000000..fa0bd82e2 --- /dev/null +++ b/modules/authlinkedin/default-disable @@ -0,0 +1,3 @@ +This file indicates that the default state of this module +is disabled. To enable, create a file named enable in the +same directory as this file. diff --git a/modules/authlinkedin/docs/oauthlinkedin.txt b/modules/authlinkedin/docs/oauthlinkedin.txt new file mode 100644 index 000000000..61937284e --- /dev/null +++ b/modules/authlinkedin/docs/oauthlinkedin.txt @@ -0,0 +1,26 @@ +Using the LinkedIn authentication source with simpleSAMLphp +=========================================================== + +Remember to configure `authsources.php`, with both Consumer key and secret. + +To get an API key and a secret, register the application at: + + * <https://www.linkedin.com/secure/developer> + +Set the callback URL to be: + + * `http://sp.example.org/simplesaml/module.php/authlinkedin/linkback.php` + +Replace `sp.example.org` with your hostname. + +## Testing authentication + +On the SimpleSAMLphp frontpage, go to the *Authentication* tab, and use the link: + + * *Test configured authentication sources* + +Then choose the *linkedin* authentication source. + +Expected behaviour would then be that you are sent to LinkedIn and asked to login. +There is no consent screen for attribute release. + diff --git a/modules/authlinkedin/lib/Auth/Source/LinkedIn.php b/modules/authlinkedin/lib/Auth/Source/LinkedIn.php new file mode 100644 index 000000000..228e02c4a --- /dev/null +++ b/modules/authlinkedin/lib/Auth/Source/LinkedIn.php @@ -0,0 +1,122 @@ +<?php + +require_once(dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/oauth/libextinc/OAuth.php'); + +/** + * Authenticate using LinkedIn. + * + * @author Brook Schofield, TERENA. + * @package simpleSAMLphp + * @version $Id$ + */ +class sspmod_authlinkedin_Auth_Source_LinkedIn extends SimpleSAML_Auth_Source { + + /** + * The string used to identify our states. + */ + const STAGE_INIT = 'authlinkedin:init'; + + /** + * The key of the AuthId field in the state. + */ + const AUTHID = 'authlinkedin:AuthId'; + + private $key; + private $secret; + + + /** + * Constructor for this authentication source. + * + * @param array $info Information about this authentication source. + * @param array $config Configuration. + */ + public function __construct($info, $config) { + assert('is_array($info)'); + assert('is_array($config)'); + + /* Call the parent constructor first, as required by the interface. */ + parent::__construct($info, $config); + + if (!array_key_exists('key', $config)) + throw new Exception('LinkedIn authentication source is not properly configured: missing [key]'); + + $this->key = $config['key']; + + if (!array_key_exists('secret', $config)) + throw new Exception('LinkedIn authentication source is not properly configured: missing [secret]'); + + $this->secret = $config['secret']; + } + + + /** + * Log-in using LinkedIn platform + * Documentation at: http://developer.linkedin.com/docs/DOC-1008 + * + * @param array &$state Information about the current authentication. + */ + public function authenticate(&$state) { + assert('is_array($state)'); + + /* We are going to need the authId in order to retrieve this authentication source later. */ + $state[self::AUTHID] = $this->authId; + + $stateID = SimpleSAML_Auth_State::getStateId($state); + SimpleSAML_Logger::debug('authlinkedin auth state id = ' . $stateID); + + $consumer = new sspmod_oauth_Consumer($this->key, $this->secret); + + // Get the request token + $requestToken = $consumer->getRequestToken('https://api.linkedin.com/uas/oauth/requestToken', array('oauth_callback' => SimpleSAML_Module::getModuleUrl('authlinkedin') . '/linkback.php?stateid=' . $stateID)); + + SimpleSAML_Logger::debug("Got a request token from the OAuth service provider [" . + $requestToken->key . "] with the secret [" . $requestToken->secret . "]"); + + $state['authlinkedin:requestToken'] = $requestToken; + + // Update the state + SimpleSAML_Auth_State::saveState($state, self::STAGE_INIT); + + // Authorize the request token + $consumer->getAuthorizeRequest('https://www.linkedin.com/uas/oauth/authenticate', $requestToken); + } + + + public function finalStep(&$state) { + $requestToken = $state['authlinkedin:requestToken']; + + $consumer = new sspmod_oauth_Consumer($this->key, $this->secret); + + SimpleSAML_Logger::debug("oauth: Using this request token [" . + $requestToken->key . "] with the secret [" . $requestToken->secret . "]"); + + // Replace the request token with an access token (via GET method) + $accessToken = $consumer->getAccessToken('https://api.linkedin.com/uas/oauth/accessToken', $requestToken, + array('oauth_verifier' => $state['authlinkedin:oauth_verifier'])); + + SimpleSAML_Logger::debug("Got an access token from the OAuth service provider [" . + $accessToken->key . "] with the secret [" . $accessToken->secret . "]"); + + // TODO: configure attributes (http://developer.linkedin.com/docs/DOC-1061) from config? Limited options via LinkedIn. + $userdata = $consumer->getUserInfo('https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,summary,specialties,picture-url)', $accessToken, array('http' => array('header' => 'x-li-format: json'))); + + $attributes = array(); + foreach($userdata AS $key => $value) { + if (is_string($value)) + $attributes['linkedin.' . $key] = array((string)$value); + + } + + // TODO: pass accessToken: key, secret + expiry as attributes? + + if (array_key_exists('id', $userdata) ) { + $attributes['linkedin_targetedID'] = array('http://linkedin.com!' . $userdata['id']); + $attributes['linkedin_user'] = array($userdata['id'] . '@linkedin.com'); + } + + SimpleSAML_Logger::debug('LinkedIn Returned Attributes: '. implode(", ",array_keys($attributes))); + + $state['Attributes'] = $attributes; + } +} diff --git a/modules/authlinkedin/www/linkback.php b/modules/authlinkedin/www/linkback.php new file mode 100644 index 000000000..961eaa6bf --- /dev/null +++ b/modules/authlinkedin/www/linkback.php @@ -0,0 +1,34 @@ +<?php + +/** + * Handle linkback() response from LinkedIn. + */ + +if (array_key_exists('stateid', $_REQUEST)) { + $stateId = $_REQUEST['stateid']; +} else { + throw new Exception('Lost OAuth Client State'); +} + +$state = SimpleSAML_Auth_State::loadState($stateId, sspmod_authlinkedin_Auth_Source_LinkedIn::STAGE_INIT); + +// http://developer.linkedin.com/docs/DOC-1008#2_Redirect_the_User_to_our_Authorization_Server +if (array_key_exists('oauth_verifier', $_REQUEST)) { + $state['authlinkedin:oauth_verifier'] = $_REQUEST['oauth_verifier']; +} else { + throw new Exception('OAuth verifier not returned.');; +} + +/* Find authentication source. */ +assert('array_key_exists(sspmod_authlinkedin_Auth_Source_LinkedIn::AUTHID, $state)'); +$sourceId = $state[sspmod_authlinkedin_Auth_Source_LinkedIn::AUTHID]; + +$source = SimpleSAML_Auth_Source::getById($sourceId); +if ($source === NULL) { + throw new Exception('Could not find authentication source with id ' . $sourceId); +} + +$source->finalStep($state); + +SimpleSAML_Auth_Source::completeAuth($state); + -- GitLab