From 927e375562753274c1aeb56842b9bae375f0744d Mon Sep 17 00:00:00 2001 From: Steven Maguire Date: Sat, 21 Mar 2015 12:46:26 -0500 Subject: [PATCH] init --- .gitignore | 4 ++ .travis.yml | 11 ++++ LICENSE | 21 +++++++ README.md | 68 ++++++++++++++++++++ composer.json | 39 ++++++++++++ phpunit.xml | 18 ++++++ src/Provider/LinkedIn.php | 74 ++++++++++++++++++++++ tests/src/Provider/LinkedInTest.php | 96 +++++++++++++++++++++++++++++ 8 files changed, 331 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 phpunit.xml create mode 100644 src/Provider/LinkedIn.php create mode 100644 tests/src/Provider/LinkedInTest.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c1fc0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/vendor +composer.phar +composer.lock +.DS_Store \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c838ecf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - hhvm + +before_script: + - travis_retry composer self-update + - travis_retry composer install --no-interaction --prefer-source --dev \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..51455e2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Steven Maguire + +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: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..034d75d --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# LinkedIn Provider for OAuth 2.0 Client + +[![Build Status](https://travis-ci.org/thephpleague/oauth2-linkedin.svg?branch=master)](https://travis-ci.org/thephpleague/oauth2-linkedin) +[![Latest Stable Version](https://poser.pugx.org/league/oauth2-linkedin/v/stable.svg)](https://packagist.org/packages/league/oauth2-linkedin) + +This package provides LinkedIn OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client). + +## Installation + +To install, use composer: + +``` +composer require league/oauth2-linkedin +``` + +## Usage + +Usage is the same as The League's OAuth client, using `\Stevenmaguire\OAuth2\Client\Provider\LinkedIn` as the provider. + +### Authorization Code Flow + +```php +$provider = new Stevenmaguire\OAuth2\Client\Provider\LinkedIn([ + 'clientId' => '{linkedin-client-id}', + 'clientSecret' => '{linkedin-client-secret}', + 'redirectUri' => 'https://example.com/callback-url' +]); + +if (!isset($_GET['code'])) { + + // If we don't have an authorization code then get one + $authUrl = $provider->getAuthorizationUrl(); + $_SESSION['oauth2state'] = $provider->state; + header('Location: '.$authUrl); + exit; + +// Check given state against previously stored one to mitigate CSRF attack +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + + unset($_SESSION['oauth2state']); + exit('Invalid state'); + +} else { + + // Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken('authorization_code', [ + 'code' => $_GET['code'] + ]); + + // Optional: Now you have a token you can look up a users profile data + try { + + // We got an access token, let's now get the user's details + $userDetails = $provider->getUserDetails($token); + + // Use these details to create a new profile + printf('Hello %s!', $userDetails->firstName); + + } catch (Exception $e) { + + // Failed to get user details + exit('Oh dear...'); + } + + // Use this to interact with an API on the users behalf + echo $token->accessToken; +} +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..77251e0 --- /dev/null +++ b/composer.json @@ -0,0 +1,39 @@ +{ + "name": "league/oauth2-linkedin", + "description": "LinkedIn OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "license": "MIT", + "authors": [ + { + "name": "Steven Maguire", + "email": "stevenmaguire@gmail.com", + "homepage": "https://github.com/stevenmaguire" + } + ], + "version": "0.1.0", + "keywords": [ + "oauth", + "oauth2", + "client", + "authorization", + "authorisation", + "linkedin" + ], + "require": { + "php": ">=5.4.0", + "league/oauth2-client": ">=0.10.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "mockery/mockery": "~0.9" + }, + "autoload": { + "psr-4": { + "Stevenmaguire\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Stevenmaguire\\OAuth2\\Client\\Test\\": "tests/src/" + } + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..e89ac6d --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./tests/ + + + \ No newline at end of file diff --git a/src/Provider/LinkedIn.php b/src/Provider/LinkedIn.php new file mode 100644 index 0000000..1fffab5 --- /dev/null +++ b/src/Provider/LinkedIn.php @@ -0,0 +1,74 @@ +fields); + return 'https://api.linkedin.com/v1/people/~:(' . $fields . ')?format=json'; + } + + public function userDetails($response, AccessToken $token) + { + $user = new User(); + + $email = (isset($response->emailAddress)) ? $response->emailAddress : null; + $location = (isset($response->location->name)) ? $response->location->name : null; + $description = (isset($response->headline)) ? $response->headline : null; + $pictureUrl = (isset($response->pictureUrl)) ? $response->pictureUrl : null; + + $user->exchangeArray([ + 'uid' => $response->id, + 'name' => $response->firstName.' '.$response->lastName, + 'firstname' => $response->firstName, + 'lastname' => $response->lastName, + 'email' => $email, + 'location' => $location, + 'description' => $description, + 'imageurl' => $pictureUrl, + 'urls' => $response->publicProfileUrl, + ]); + + return $user; + } + + public function userUid($response, AccessToken $token) + { + return $response->id; + } + + public function userEmail($response, AccessToken $token) + { + return isset($response->emailAddress) && $response->emailAddress + ? $response->emailAddress + : null; + } + + public function userScreenName($response, AccessToken $token) + { + return [$response->firstName, $response->lastName]; + } +} diff --git a/tests/src/Provider/LinkedInTest.php b/tests/src/Provider/LinkedInTest.php new file mode 100644 index 0000000..2c270e6 --- /dev/null +++ b/tests/src/Provider/LinkedInTest.php @@ -0,0 +1,96 @@ +provider = new \League\OAuth2\Client\Provider\LinkedIn([ + 'clientId' => 'mock_client_id', + 'clientSecret' => 'mock_secret', + 'redirectUri' => 'none', + ]); + } + + public function tearDown() + { + m::close(); + parent::tearDown(); + } + + public function testAuthorizationUrl() + { + $url = $this->provider->getAuthorizationUrl(); + $uri = parse_url($url); + parse_str($uri['query'], $query); + + $this->assertArrayHasKey('client_id', $query); + $this->assertArrayHasKey('redirect_uri', $query); + $this->assertArrayHasKey('state', $query); + $this->assertArrayHasKey('scope', $query); + $this->assertArrayHasKey('response_type', $query); + $this->assertArrayHasKey('approval_prompt', $query); + $this->assertNotNull($this->provider->state); + } + + public function testUrlAccessToken() + { + $url = $this->provider->urlAccessToken(); + $uri = parse_url($url); + + $this->assertEquals('/uas/oauth2/accessToken', $uri['path']); + } + + public function testGetAccessToken() + { + $response = m::mock('Guzzle\Http\Message\Response'); + $response->shouldReceive('getBody')->times(1)->andReturn('{"access_token": "mock_access_token", "expires": 3600, "refresh_token": "mock_refresh_token", "uid": 1}'); + + $client = m::mock('Guzzle\Service\Client'); + $client->shouldReceive('setBaseUrl')->times(1); + $client->shouldReceive('post->send')->times(1)->andReturn($response); + $this->provider->setHttpClient($client); + + $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + + $this->assertEquals('mock_access_token', $token->accessToken); + $this->assertLessThanOrEqual(time() + 3600, $token->expires); + $this->assertGreaterThanOrEqual(time(), $token->expires); + $this->assertEquals('mock_refresh_token', $token->refreshToken); + $this->assertEquals('1', $token->uid); + } + + public function testScopes() + { + $this->assertEquals(['r_basicprofile r_emailaddress r_contactinfo'], $this->provider->getScopes()); + } + + public function testUserData() + { + $postResponse = m::mock('Guzzle\Http\Message\Response'); + $postResponse->shouldReceive('getBody')->times(1)->andReturn('{"access_token": "mock_access_token", "expires": 3600, "refresh_token": "mock_refresh_token", "uid": 1}'); + + $getResponse = m::mock('Guzzle\Http\Message\Response'); + $getResponse->shouldReceive('getBody')->times(4)->andReturn('{"id": 12345, "firstName": "mock_first_name", "lastName": "mock_last_name", "emailAddress": "mock_email", "location": { "name": "mock_location" }, "headline": "mock_headline", "pictureUrl": "mock_picture_url", "publicProfileUrl": "mock_profile_url"}'); + + $client = m::mock('Guzzle\Service\Client'); + $client->shouldReceive('setBaseUrl')->times(5); + $client->shouldReceive('setDefaultOption')->times(4); + $client->shouldReceive('post->send')->times(1)->andReturn($postResponse); + $client->shouldReceive('get->send')->times(4)->andReturn($getResponse); + $this->provider->setHttpClient($client); + + $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + $user = $this->provider->getUserDetails($token); + + $this->assertEquals(12345, $this->provider->getUserUid($token)); + $this->assertEquals(['mock_first_name', 'mock_last_name'], $this->provider->getUserScreenName($token)); + $this->assertEquals('mock_email', $this->provider->getUserEmail($token)); + $this->assertEquals('mock_email', $user->email); + } +}