Repository: Happyr/LinkedIn-API-client Branch: master Commit: 58bb3bca346e Files: 40 Total size: 99.5 KB Directory structure: gitextract__ze4uh7e/ ├── .github/ │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .scrutinizer.yml ├── .styleci.yml ├── .travis.yml ├── LICENSE ├── Readme.md ├── Upgrade.md ├── composer.json ├── phpunit.xml.dist ├── src/ │ ├── AccessToken.php │ ├── Authenticator.php │ ├── AuthenticatorInterface.php │ ├── Exception/ │ │ ├── InvalidArgumentException.php │ │ ├── LinkedInException.php │ │ ├── LinkedInTransferException.php │ │ └── LoginError.php │ ├── Http/ │ │ ├── CurrentUrlGeneratorInterface.php │ │ ├── GlobalVariableGetter.php │ │ ├── LinkedInUrlGeneratorInterface.php │ │ ├── RequestManager.php │ │ ├── RequestManagerInterface.php │ │ ├── ResponseConverter.php │ │ ├── UrlGenerator.php │ │ └── UrlGeneratorInterface.php │ ├── LinkedIn.php │ ├── LinkedInInterface.php │ └── Storage/ │ ├── BaseDataStorage.php │ ├── DataStorageInterface.php │ ├── IlluminateSessionStorage.php │ └── SessionStorage.php └── tests/ ├── AccessTokenTest.php ├── AuthenticatorTest.php ├── Exceptions/ │ └── LoginErrorTest.php ├── Http/ │ ├── ResponseConverterTest.php │ └── UrlGeneratorTest.php ├── LinkedInTest.php └── Storage/ ├── IlluminateSessionStorageTest.php └── SessionStorageTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ | Q | A | ----------------------- | --- | Bug? | no|yes | New Feature? | no|yes | Are you using composer? | no|yes | Version | Specific version or SHA of a commit #### Actual Behavior What is the actual behavior? #### Expected Behavior What is the behavior you expect? #### Steps to Reproduce What are the steps to reproduce this bug? Please add code examples, screenshots or links to GitHub repositories that reproduce the problem. #### Possible Solutions If you have already ideas how to solve the issue, add them here. (remove this section if not needed) ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ | Q | A | --------------- | --- | Bug fix? | no|yes | New feature? | no|yes | BC breaks? | no|yes | Deprecations? | no|yes | Related tickets | fixes #X, partially #Y, mentioned in #Z | License | MIT #### What's in this PR? Explain what the changes in this PR do. #### Why? Which problem does the PR fix? (remove this section if you linked an issue above) #### Example Usage ``` php // If you added new features, show examples of how to use them here // (remove this section if not a new feature) $foo = new Foo(); // Now we can do $foo->doSomething(); ``` #### Checklist - [ ] Updated CHANGELOG.md to describe BC breaks / deprecations | new feature | bugfix - [ ] Documentation pull request created (if not simply a bugfix) #### To Do - [ ] If the PR is not complete but you want to discuss the approach, list what remains to be done here ================================================ FILE: .gitignore ================================================ /build/ composer.lock index.php phpunit.xml /vendor/ ================================================ FILE: .scrutinizer.yml ================================================ filter: paths: [src/*] checks: php: code_rating: true duplication: true tools: external_code_coverage: true ================================================ FILE: .styleci.yml ================================================ preset: symfony finder: path: - "src" - "tests" enabled: - short_array_syntax disabled: - phpdoc_annotation_without_dot ================================================ FILE: .travis.yml ================================================ language: php sudo: false cache: directories: - $HOME/.composer/cache/files php: - 5.5 - 5.6 - 7.0 - 7.1 - hhvm env: global: - TEST_COMMAND="composer test" matrix: fast_finish: true include: - php: 5.5 env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" COVERAGE=true TEST_COMMAND="composer test-ci" before_install: - composer self-update install: - travis_retry composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction script: - $TEST_COMMAND after_success: - if [[ $COVERAGE = true ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi - if [[ $COVERAGE = true ]]; then php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml; fi ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Tobias Nyholm 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. ================================================ FILE: Readme.md ================================================ # LinkedIn API client in PHP [![Latest Version](https://img.shields.io/github/release/Happyr/LinkedIn-API-client.svg?style=flat-square)](https://github.com/Happyr/LinkedIn-API-client/releases) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) [![Build Status](https://img.shields.io/travis/Happyr/LinkedIn-API-client/master.svg?style=flat-square)](https://travis-ci.org/Happyr/LinkedIn-API-client) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/44c425af-90f6-4c25-b789-4ece28b01a2b/mini.png)](https://insight.sensiolabs.com/projects/44c425af-90f6-4c25-b789-4ece28b01a2b) [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/Happyr/LinkedIn-API-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/Happyr/LinkedIn-API-client) [![Quality Score](https://img.shields.io/scrutinizer/g/Happyr/LinkedIn-API-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/Happyr/LinkedIn-API-client) [![Total Downloads](https://img.shields.io/packagist/dt/happyr/linkedin-api-client.svg?style=flat-square)](https://packagist.org/packages/happyr/linkedin-api-client) A PHP library to handle authentication and communication with LinkedIn API. The library/SDK helps you to get an access token and when authenticated it helps you to send API requests. You will not get *everything* for free though... You have to read the [LinkedIn documentation][api-doc-core] to understand how you should query the API. To get an overview what this library actually is doing for you. Take a look at the authentication page from the [API docs][api-doc-authentication]. ## Features Here is a list of features that might convince you to choose this LinkedIn client over some of our competitors'. * Flexible and easy to extend * Developed with modern PHP standards * Not developed for a specific framework. * Handles the authentication process * Respects the CSRF protection ## Installation **TL;DR** ```bash composer require php-http/curl-client guzzlehttp/psr7 php-http/message happyr/linkedin-api-client ``` This library does not have a dependency on Guzzle or any other library that sends HTTP requests. We use the awesome HTTPlug to achieve the decoupling. We want you to choose what library to use for sending HTTP requests. Consult this list of packages that support [php-http/client-implementation](https://packagist.org/providers/php-http/client-implementation) find clients to use. For more information about virtual packages please refer to [HTTPlug](http://docs.php-http.org/en/latest/httplug/users.html). Example: ```bash composer require php-http/guzzle6-adapter ``` You do also need to install a PSR-7 implementation and a factory to create PSR-7 messages (PSR-17 whenever that is released). You could use Guzzles PSR-7 implementation and factories from php-http: ```bash composer require guzzlehttp/psr7 php-http/message ``` Now you may install the library by running the following: ```bash composer require happyr/linkedin-api-client ``` If you are updating form a previous version make sure to read [the upgrade documentation](Upgrade.md). ### Finding the HTTP client (optional) The LinkedIn client need to know what library you are using to send HTTP messages. You could provide an instance of HttpClient and MessageFactory or you could fallback on auto discovery. Below is an example on where you provide a Guzzle6 instance. ```php $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret'); $linkedIn->setHttpClient(new \Http\Adapter\Guzzle6\Client()); $linkedIn->setHttpMessageFactory(new Http\Message\MessageFactory\GuzzleMessageFactory()); ``` ## Usage In order to use this API client (or any other LinkedIn clients) you have to [register your application][register-app] with LinkedIn to receive an API key. Once you've registered your LinkedIn app, you will be provided with an *API Key* and *Secret Key*. ### LinkedIn login This example below is showing how to login with LinkedIn. ```php isAuthenticated()) { //we know that the user is authenticated now. Start query the API $user=$linkedIn->get('v1/people/~:(firstName,lastName)'); echo "Welcome ".$user['firstName']; exit(); } elseif ($linkedIn->hasError()) { echo "User canceled the login."; exit(); } //if not authenticated $url = $linkedIn->getLoginUrl(); echo "Login with LinkedIn"; ``` ### How to post on LinkedIn wall The example below shows how you can post on a users wall. The access token is fetched from the database. ```php $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret'); $linkedIn->setAccessToken('access_token_from_db'); $options = array('json'=> array( 'comment' => 'Im testing Happyr LinkedIn client! https://github.com/Happyr/LinkedIn-API-client', 'visibility' => array( 'code' => 'anyone' ) ) ); $result = $linkedIn->post('v1/people/~/shares', $options); var_dump($result); // Prints: // array (size=2) // 'updateKey' => string 'UPDATE-01234567-0123456789012345678' (length=35) // 'updateUrl' => string 'https://www.linkedin.com/updates?discuss=&scope=01234567&stype=M&topic=0123456789012345678&type=U&a=mVKU' (length=104) ``` You may of course do the same in xml. Use the following options array. ```php $options = array( 'format' => 'xml', 'body' => ' Im testing Happyr LinkedIn client! https://github.com/Happyr/LinkedIn-API-client anyone '); ``` ## Configuration ### The api options The third parameter of `LinkedIn::api` is an array with options. Below is a table of array keys that you may use. | Option name | Description | ----------- | ----------- | body | The body of a HTTP request. Put your xml string here. | format | Set this to 'json', 'xml' or 'simple_xml' to override the default value. | headers | This is HTTP headers to the request | json | This is an array with json data that will be encoded to a json string. Using this option you do need to specify a format. | response_data_type | To override the response format for one request | query | This is an array with query parameters ### Changing request format The default format when communicating with LinkedIn API is json. You can let the API do `json_encode` for you. The following code shows you how. ```php $body = array( 'comment' => 'Im testing Happyr LinkedIn client! https://github.com/Happyr/LinkedIn-API-client', 'visibility' => array('code' => 'anyone') ); $linkedIn->post('v1/people/~/shares', array('json'=>$body)); $linkedIn->post('v1/people/~/shares', array('body'=>json_encode($body))); ``` When using `array('json'=>$body)` as option the format will always be `json`. You can change the request format in three ways. ```php // By constructor argument $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret', 'xml'); // By setter $linkedIn->setFormat('xml'); // Set format for just one request $linkedIn->post('v1/people/~/shares', array('format'=>'xml', 'body'=>$body)); ``` ### Understanding response data type The data type returned from `LinkedIn::api` can be configured. You may use the forth construtor argument, the `LinkedIn::setResponseDataType` or as an option for `LinkedIn::api` ```php // By constructor argument $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret', 'json', 'array'); // By setter $linkedIn->setResponseDataType('simple_xml'); // Set format for just one request $linkedIn->get('v1/people/~:(firstName,lastName)', array('response_data_type'=>'psr7')); ``` Below is a table that specifies what the possible return data types are when you call `LinkedIn::api`. | Type | Description | ------ | ------------ | array | An assosiative array. This can only be used with the `json` format. | simple_xml | A SimpleXMLElement. See [PHP manual](http://php.net/manual/en/class.simplexmlelement.php). This can only be used with the `xml` format. | psr7 | A PSR7 response. | stream | A file stream. | string | A plain old string. ### Use different Session classes You might want to use an other storage than the default `SessionStorage`. If you are using Laravel you are more likely to inject the `IlluminateSessionStorage`. ```php $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret'); $linkedIn->setStorage(new IlluminateSessionStorage()); ``` You can inject any class implementing `DataStorageInterface`. You can also inject different `UrlGenerator` classes. ### Using different scopes If you want to define special scopes when you authenticate the user you should specify them when you are generating the login url. If you don't specify scopes LinkedIn will use the default scopes that you have configured for the app. ```php $scope = 'r_fullprofile,r_emailaddress,w_share'; //or $scope = array('rw_groups', 'r_contactinfo', 'r_fullprofile', 'w_messages'); $url = $linkedIn->getLoginUrl(array('scope'=>$scope)); echo "Login with LinkedIn"; ``` ## Framework integration If you want an easier integration with a framwork you may want to check out these repositories: * [HappyrLinkedInBundle](https://github.com/Happyr/LinkedInBundle) for Symfony * [Laravel-Linkedin by mauri870](https://github.com/artesaos/laravel-linkedin) for Laravel 5 [register-app]: https://www.linkedin.com/secure/developer [linkedin-code-samples]: https://developer.linkedin.com/documents/code-samples [api-doc-authentication]: https://developer.linkedin.com/documents/authentication [api-doc-core]: https://developer.linkedin.com/core-concepts ================================================ FILE: Upgrade.md ================================================ # Upgrade This document explains how you upgrade from one version to another. ## Upgrade from 0.7.2 to 1.0 ### Changes * We do not longer require `php-http/message`. You have to make sure to put that in your own composer.json. ## Upgrade from 0.7.1 to 0.7.2 ### Changes * Using `php-http/discovery:1.0` * Code style changes. ## Upgrade from 0.7.0 to 0.7.1 ### Changes * Using `php-http/discovery:0.9` which makes Puli optional * Using new URL's to LinkedIn API so users are provided with the new authentication UX. (Thanks to @mbarwick83) ## Upgrade from 0.6 to 0.7 ### Changes * Introduced PHP-HTTP and PSR-7 messages * Added constructor argument for responseDataType * Added setResponseDataType() * Moved authentication functions to `Authenticator` class To make sure you can upgrade you need to install a HTTP adapter. ```bash php composer.phar require php-http/guzzle6-adapter ``` ### BC breaks * Removed `LinkedIn::setRequest` in favor of `LinkedIn::setHttpAdapter` * Removed `LinkedIn::getAppSecret` and `LinkedIn::getAppId` * Removed `LinkedIn::getUser` * Removed `LinkedInApiException` in favor of `LinkedInException`, `InvalidArgumentException` and `LinkedInTransferException` * Removed `LinkedIn::getLastHeaders` in favor of `LinkedIn::getLastResponse` * Made the public functions `LinkedIn::getResponseDataType` and `LinkedIn::getFormat` protected ## Upgrade from 0.5 to 0.6 ### Changes * When exchanging the code for an access token we are now using the post body instead of query parameters * Better error handling when exchange from code to access token fails ### BC breaks There are a few minor BC breaks. We removed the functions below: * `LinkedIn::getUserId`, use `LinkedIn::getUser` instead * `AccessToken::constructFromJson`, Use the constructor instead. ## Upgrade from 0.4 to 0.5 ### Changed signature of `LinkedIn::api` The signature of `LinkedIn::api` has changed to be more easy to work with. ```php // Version 0.4 public function api($resource, array $urlParams=array(), $method='GET', $postParams=array()) // Version 0.5 public function api($method, $resource, array $options=array()) ``` This means that you have to modify your calls to: ```php // Version 0.5 $options = array('query'=>$urlParams, 'body'=>$postParams); $linkedIn->api('POST', $resource, $options) ``` See the Readme about more options to the API function. ### Must inject IlluminateSessionStorage We have removed the protected `LinkedIn::init` function. That means if you were using `IlluminateSessionStorage` you have to make a minor adjustment to your code. ```php // Version 0.4 $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret'); // Version 0.5 $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret'); $linkedIn->setStorage(new IlluminateSessionStorage()); ``` If you don't know about `IlluminateSessionStorage` you are probably good ignoring this. ### Default format The default format when communicating with LinkedIn API is changed to json. ### Updated RequestInterface The `RequestInterface::send` was updated with a new signature. We did also introduce `RequestInterface::getHeadersFromLastResponse`. ================================================ FILE: composer.json ================================================ { "name": "happyr/linkedin-api-client", "type": "library", "description": "LinkedIn API client. Handles OAuth, CSRF protection. Easy to implement and extend. This is a standalone library for any composer project.", "keywords": ["LinkedIn", "OAuth", "API", "Client", "SDK"], "homepage": "http://developer.happyr.com/libraries/linkedin-php-client", "license": "MIT", "authors": [ { "name": "Tobias Nyholm", "email": "tobias@happyr.com" } ], "require": { "php": "^5.5 || ^7.0", "php-http/client-implementation": "^1.0", "php-http/httplug": "^1.0", "php-http/message-factory": "^1.0", "php-http/discovery": "^1.0" }, "require-dev": { "phpunit/phpunit": "^4.5 || ^5.0", "php-http/guzzle5-adapter": "^1.0", "guzzlehttp/psr7": "^1.2", "mockery/mockery": "^0.9", "illuminate/support": "^5.0" }, "autoload": { "psr-4": { "Happyr\\LinkedIn\\": "src/" } }, "scripts": { "test": "vendor/bin/phpunit", "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml" }, "extra": { "branch-alias": { "dev-master": "1.1.x-dev" } } } ================================================ FILE: phpunit.xml.dist ================================================ ./tests src vendor tests ================================================ FILE: src/AccessToken.php ================================================ token = $token; if ($expiresIn !== null) { if ($expiresIn instanceof \DateTime) { $this->expiresAt = $expiresIn; } else { $this->expiresAt = new \DateTime(sprintf('+%dseconds', $expiresIn)); } } } /** * @return string */ public function __toString() { return $this->token ?: ''; } /** * Does a token string exist? * * @return bool */ public function hasToken() { return !empty($this->token); } /** * @param \DateTime $expiresAt * * @return $this */ public function setExpiresAt(\DateTime $expiresAt = null) { $this->expiresAt = $expiresAt; return $this; } /** * @return \DateTime */ public function getExpiresAt() { return $this->expiresAt; } /** * @param null|string $token * * @return $this */ public function setToken($token) { $this->token = $token; return $this; } /** * @return null|string */ public function getToken() { return $this->token; } } ================================================ FILE: src/Authenticator.php ================================================ */ class Authenticator implements AuthenticatorInterface { /** * The application ID. * * @var string */ protected $appId; /** * The application secret. * * @var string */ protected $appSecret; /** * A storage to use to store data between requests. * * @var DataStorageInterface storage */ private $storage; /** * @var RequestManagerInterface */ private $requestManager; /** * @param RequestManagerInterface $requestManager * @param string $appId * @param string $appSecret */ public function __construct(RequestManagerInterface $requestManager, $appId, $appSecret) { $this->appId = $appId; $this->appSecret = $appSecret; $this->requestManager = $requestManager; } /** * {@inheritdoc} */ public function fetchNewAccessToken(LinkedInUrlGeneratorInterface $urlGenerator) { $storage = $this->getStorage(); $code = $this->getCode(); if ($code === null) { /* * As a fallback, just return whatever is in the persistent * store, knowing nothing explicit (signed request, authorization * code, etc.) was present to shadow it. */ return $storage->get('access_token'); } try { $accessToken = $this->getAccessTokenFromCode($urlGenerator, $code); } catch (LinkedInException $e) { // code was bogus, so everything based on it should be invalidated. $storage->clearAll(); throw $e; } $storage->set('code', $code); $storage->set('access_token', $accessToken); return $accessToken; } /** * Retrieves an access token for the given authorization code * (previously generated from www.linkedin.com on behalf of * a specific user). The authorization code is sent to www.linkedin.com * and a legitimate access token is generated provided the access token * and the user for which it was generated all match, and the user is * either logged in to LinkedIn or has granted an offline access permission. * * @param LinkedInUrlGeneratorInterface $urlGenerator * @param string $code An authorization code. * * @return AccessToken An access token exchanged for the authorization code. * * @throws LinkedInException */ protected function getAccessTokenFromCode(LinkedInUrlGeneratorInterface $urlGenerator, $code) { if (empty($code)) { throw new LinkedInException('Could not get access token: The code was empty.'); } $redirectUri = $this->getStorage()->get('redirect_uri'); try { $url = $urlGenerator->getUrl('www', 'oauth/v2/accessToken'); $headers = ['Content-Type' => 'application/x-www-form-urlencoded']; $body = http_build_query( [ 'grant_type' => 'authorization_code', 'code' => $code, 'redirect_uri' => $redirectUri, 'client_id' => $this->appId, 'client_secret' => $this->appSecret, ] ); $response = ResponseConverter::convertToArray($this->getRequestManager()->sendRequest('POST', $url, $headers, $body)); } catch (LinkedInTransferException $e) { // most likely that user very recently revoked authorization. // In any event, we don't have an access token, so throw an exception. throw new LinkedInException('Could not get access token: The user may have revoked the authorization response from LinkedIn.com was empty.', $e->getCode(), $e); } if (empty($response)) { throw new LinkedInException('Could not get access token: The response from LinkedIn.com was empty.'); } $tokenData = array_merge(['access_token' => null, 'expires_in' => null], $response); $token = new AccessToken($tokenData['access_token'], $tokenData['expires_in']); if (!$token->hasToken()) { throw new LinkedInException('Could not get access token: The response from LinkedIn.com did not contain a token.'); } return $token; } /** * {@inheritdoc} */ public function getLoginUrl(LinkedInUrlGeneratorInterface $urlGenerator, $options = []) { // Generate a state $this->establishCSRFTokenState(); // Build request params $requestParams = array_merge([ 'response_type' => 'code', 'client_id' => $this->appId, 'state' => $this->getStorage()->get('state'), 'redirect_uri' => null, ], $options); // Save the redirect url for later $this->getStorage()->set('redirect_uri', $requestParams['redirect_uri']); // if 'scope' is passed as an array, convert to space separated list $scopeParams = isset($options['scope']) ? $options['scope'] : null; if ($scopeParams) { //if scope is an array if (is_array($scopeParams)) { $requestParams['scope'] = implode(' ', $scopeParams); } elseif (is_string($scopeParams)) { //if scope is a string with ',' => make it to an array $requestParams['scope'] = str_replace(',', ' ', $scopeParams); } } return $urlGenerator->getUrl('www', 'oauth/v2/authorization', $requestParams); } /** * Get the authorization code from the query parameters, if it exists, * and otherwise return null to signal no authorization code was * discovered. * * @return string|null The authorization code, or null if the authorization code not exists. * * @throws LinkedInException on invalid CSRF tokens */ protected function getCode() { $storage = $this->getStorage(); if (!GlobalVariableGetter::has('code')) { return; } if ($storage->get('code') === GlobalVariableGetter::get('code')) { //we have already validated this code return; } // if stored state does not exists if (null === $state = $storage->get('state')) { throw new LinkedInException('Could not find a stored CSRF state token.'); } // if state not exists in the request if (!GlobalVariableGetter::has('state')) { throw new LinkedInException('Could not find a CSRF state token in the request.'); } // if state exists in session and in request and if they are not equal if ($state !== GlobalVariableGetter::get('state')) { throw new LinkedInException('The CSRF state token from the request does not match the stored token.'); } // CSRF state has done its job, so clear it $storage->clear('state'); return GlobalVariableGetter::get('code'); } /** * Lays down a CSRF state token for this process. */ protected function establishCSRFTokenState() { $storage = $this->getStorage(); if ($storage->get('state') === null) { $storage->set('state', md5(uniqid(mt_rand(), true))); } } /** * {@inheritdoc} */ public function clearStorage() { $this->getStorage()->clearAll(); return $this; } /** * @return DataStorageInterface */ protected function getStorage() { if ($this->storage === null) { $this->storage = new SessionStorage(); } return $this->storage; } /** * {@inheritdoc} */ public function setStorage(DataStorageInterface $storage) { $this->storage = $storage; return $this; } /** * @return RequestManager */ protected function getRequestManager() { return $this->requestManager; } } ================================================ FILE: src/AuthenticatorInterface.php ================================================ */ interface AuthenticatorInterface { /** * Tries to get a new access token from data storage or code. If it fails, it will return null. * * @param LinkedInUrlGeneratorInterface $urlGenerator * * @return AccessToken|null A valid user access token, or null if one could not be fetched. * * @throws LinkedInException */ public function fetchNewAccessToken(LinkedInUrlGeneratorInterface $urlGenerator); /** * Generate a login url. * * @param LinkedInUrlGeneratorInterface $urlGenerator * @param array $options * * @return string */ public function getLoginUrl(LinkedInUrlGeneratorInterface $urlGenerator, $options = []); /** * Clear the storage. * * @return $this */ public function clearStorage(); /** * @param DataStorageInterface $storage * * @return $this */ public function setStorage(DataStorageInterface $storage); } ================================================ FILE: src/Exception/InvalidArgumentException.php ================================================ */ class LinkedInException extends \Exception { } ================================================ FILE: src/Exception/LinkedInTransferException.php ================================================ */ class LinkedInTransferException extends LinkedInException { } ================================================ FILE: src/Exception/LoginError.php ================================================ name = $name; $this->description = $description; } /** * @return string */ public function getName() { return $this->name; } /** * @return string */ public function getDescription() { return $this->description; } /** * @return string */ public function __toString() { return sprintf('Name: %s, Description: %s', $this->getName(), $this->getDescription()); } } ================================================ FILE: src/Http/CurrentUrlGeneratorInterface.php ================================================ */ interface CurrentUrlGeneratorInterface { /** * Returns the current URL. * * @return string The current URL */ public function getCurrentUrl(); /** * Should we trust forwarded headers? * * @param bool $trustForwarded * * @return $this */ public function setTrustForwarded($trustForwarded); } ================================================ FILE: src/Http/GlobalVariableGetter.php ================================================ */ class GlobalVariableGetter { /** * Returns true iff the $_REQUEST or $_GET variables has a key with $name. * * @param string $name * * @return bool */ public static function has($name) { if (isset($_REQUEST[$name])) { return true; } return isset($_GET[$name]); } /** * Returns the value in $_REQUEST[$name] or $_GET[$name] if the former was empty. If no value found, return null. * * @param string $name * * @return mixed|null */ public static function get($name) { if (isset($_REQUEST[$name])) { return $_REQUEST[$name]; } if (isset($_GET[$name])) { return $_GET[$name]; } return; } } ================================================ FILE: src/Http/LinkedInUrlGeneratorInterface.php ================================================ */ interface LinkedInUrlGeneratorInterface { /** * Build the URL for given domain alias, path and parameters. * * @param $name string The name of the domain, 'www' or 'api' * @param $path string without a leading slash * @param $params array query parameters * * @return string The URL for the given parameters. The URL query MUST be build with PHP_QUERY_RFC3986 */ public function getUrl($name, $path = '', $params = []); } ================================================ FILE: src/Http/RequestManager.php ================================================ */ class RequestManager implements RequestManagerInterface { /** * @var \Http\Client\HttpClient */ private $httpClient; /** * @var \Http\Message\MessageFactory */ private $messageFactory; /** * {@inheritdoc} */ public function sendRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1') { $request = $this->getMessageFactory()->createRequest($method, $uri, $headers, $body, $protocolVersion); try { return $this->getHttpClient()->sendRequest($request); } catch (TransferException $e) { throw new LinkedInTransferException('Error while requesting data from LinkedIn.com: '.$e->getMessage(), $e->getCode(), $e); } } /** * {@inheritdoc} */ public function setHttpClient(HttpClient $httpClient) { $this->httpClient = $httpClient; return $this; } /** * @return HttpClient */ protected function getHttpClient() { if ($this->httpClient === null) { $this->httpClient = HttpClientDiscovery::find(); } return $this->httpClient; } /** * @param MessageFactory $messageFactory * * @return RequestManager */ public function setMessageFactory(MessageFactory $messageFactory) { $this->messageFactory = $messageFactory; return $this; } /** * @return \Http\Message\MessageFactory */ private function getMessageFactory() { if ($this->messageFactory === null) { $this->messageFactory = MessageFactoryDiscovery::find(); } return $this->messageFactory; } } ================================================ FILE: src/Http/RequestManagerInterface.php ================================================ */ interface RequestManagerInterface { /** * Send a request. * * @param string $method * @param string $uri * @param array $headers * @param string $body * @param string $protocolVersion * * @return \Psr\Http\Message\ResponseInterface * * @throws LinkedInTransferException */ public function sendRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'); /** * @param \Http\Client\HttpClient $httpClient * * @return RequestManager */ public function setHttpClient(HttpClient $httpClient); } ================================================ FILE: src/Http/ResponseConverter.php ================================================ getBody()->__toString(); case 'simple_xml': return self::convertToSimpleXml($response); case 'stream': return $response->getBody(); case 'psr7': return $response; default: throw new InvalidArgumentException('Format "%s" is not supported', $dataType); } } /** * @param ResponseInterface $response * * @return string */ public static function convertToArray(ResponseInterface $response) { return json_decode($response->getBody(), true); } /** * @param ResponseInterface $response * * @return \SimpleXMLElement * * @throws LinkedInTransferException */ public static function convertToSimpleXml(ResponseInterface $response) { $body = $response->getBody(); try { return new \SimpleXMLElement((string) $body ?: ''); } catch (\Exception $e) { throw new LinkedInTransferException('Unable to parse response body into XML.'); } } } ================================================ FILE: src/Http/UrlGenerator.php ================================================ */ class UrlGenerator implements UrlGeneratorInterface { /** * @var array knownLinkedInParams * * A list of params that might be in the query string */ public static $knownLinkedInParams = ['state', 'code', 'access_token', 'user']; /** * @var array domainMap * * Maps aliases to LinkedIn domains. */ public static $domainMap = [ 'api' => 'https://api.linkedin.com/', 'www' => 'https://www.linkedin.com/', ]; /** * @var bool * * Indicates if we trust HTTP_X_FORWARDED_* headers. */ protected $trustForwarded = false; /** * {@inheritdoc} */ public function getUrl($name, $path = '', $params = []) { $url = self::$domainMap[$name]; if ($path) { if ($path[0] === '/') { $path = substr($path, 1); } $url .= $path; } if (!empty($params)) { // does it exist a query string? $queryString = parse_url($url, PHP_URL_QUERY); if (empty($queryString)) { $url .= '?'; } else { $url .= '&'; } // it needs to be PHP_QUERY_RFC3986. We want to have %20 between scopes $url .= http_build_query($params, null, '&', PHP_QUERY_RFC3986); } return $url; } /** * {@inheritdoc} */ public function getCurrentUrl() { $protocol = $this->getHttpProtocol().'://'; $host = $this->getHttpHost(); $currentUrl = $protocol.$host.$_SERVER['REQUEST_URI']; $parts = parse_url($currentUrl); $query = ''; if (!empty($parts['query'])) { // drop known linkedin params $query = $this->dropLinkedInParams($parts['query']); } // use port if non default $port = isset($parts['port']) && (($protocol === 'http://' && $parts['port'] !== 80) || ($protocol === 'https://' && $parts['port'] !== 443)) ? ':'.$parts['port'] : ''; // rebuild return $protocol.$parts['host'].$port.$parts['path'].$query; } /** * Drop known LinkedIn params. Ie those in self::$knownLinkeInParams. * * @param string $query * * @return string query without LinkedIn params. This string is prepended with a question mark '?' */ protected function dropLinkedInParams($query) { if ($query == '') { return ''; } $params = explode('&', $query); foreach ($params as $i => $param) { /* * A key or key/value pair might me 'foo=bar', 'foo=', or 'foo'. */ //get the first value of the array you will get when you explode() list($key) = explode('=', $param, 2); if (in_array($key, self::$knownLinkedInParams)) { unset($params[$i]); } } //assert: params is an array. It might be empty if (!empty($params)) { return '?'.implode($params, '&'); } return ''; } /** * Get the host. * * * @return mixed */ protected function getHttpHost() { if ($this->trustForwarded && isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { return $_SERVER['HTTP_X_FORWARDED_HOST']; } return $_SERVER['HTTP_HOST']; } /** * Get the protocol. * * * @return string */ protected function getHttpProtocol() { if ($this->trustForwarded && isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { if ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { return 'https'; } return 'http'; } /*apache + variants specific way of checking for https*/ if (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] == 1)) { return 'https'; } /*nginx way of checking for https*/ if (isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] === '443')) { return 'https'; } return 'http'; } /** * {@inheritdoc} */ public function setTrustForwarded($trustForwarded) { $this->trustForwarded = $trustForwarded; return $this; } } ================================================ FILE: src/Http/UrlGeneratorInterface.php ================================================ */ interface UrlGeneratorInterface extends LinkedInUrlGeneratorInterface, CurrentUrlGeneratorInterface { } ================================================ FILE: src/LinkedIn.php ================================================ */ class LinkedIn implements LinkedInInterface { /** * The OAuth access token received in exchange for a valid authorization * code. null means the access token has yet to be determined. * * @var AccessToken */ protected $accessToken = null; /** * @var string format */ private $format; /** * @var string responseFormat */ private $responseDataType; /** * @var ResponseInterface */ private $lastResponse; /** * @var RequestManager */ private $requestManager; /** * @var Authenticator */ private $authenticator; /** * @var UrlGeneratorInterface */ private $urlGenerator; /** * Constructor. * * @param string $appId * @param string $appSecret * @param string $format 'json', 'xml' * @param string $responseDataType 'array', 'string', 'simple_xml' 'psr7', 'stream' */ public function __construct($appId, $appSecret, $format = 'json', $responseDataType = 'array') { $this->format = $format; $this->responseDataType = $responseDataType; $this->requestManager = new RequestManager(); $this->authenticator = new Authenticator($this->requestManager, $appId, $appSecret); } /** * {@inheritdoc} */ public function isAuthenticated() { $accessToken = $this->getAccessToken(); if ($accessToken === null) { return false; } $user = $this->api('GET', '/v1/people/~:(id,firstName,lastName)', ['format' => 'json', 'response_data_type' => 'array']); return !empty($user['id']); } /** * {@inheritdoc} */ public function api($method, $resource, array $options = []) { // Add access token to the headers $options['headers']['Authorization'] = sprintf('Bearer %s', (string) $this->getAccessToken()); // Do logic and adjustments to the options $requestFormat = $this->filterRequestOption($options); // Generate an url $url = $this->getUrlGenerator()->getUrl( 'api', $resource, isset($options['query']) ? $options['query'] : [] ); $body = isset($options['body']) ? $options['body'] : null; $this->lastResponse = $this->getRequestManager()->sendRequest($method, $url, $options['headers'], $body); //Get the response data format if (isset($options['response_data_type'])) { $responseDataType = $options['response_data_type']; } else { $responseDataType = $this->getResponseDataType(); } return ResponseConverter::convert($this->lastResponse, $requestFormat, $responseDataType); } /** * Modify and filter the request options. Make sure we use the correct query parameters and headers. * * @param array &$options * * @return string the request format to use */ protected function filterRequestOption(array &$options) { if (isset($options['json'])) { $options['format'] = 'json'; $options['body'] = json_encode($options['json']); } elseif (!isset($options['format'])) { // Make sure we always have a format $options['format'] = $this->getFormat(); } // Set correct headers for this format switch ($options['format']) { case 'xml': $options['headers']['Content-Type'] = 'text/xml'; break; case 'json': $options['headers']['Content-Type'] = 'application/json'; $options['headers']['x-li-format'] = 'json'; $options['query']['format'] = 'json'; break; default: // Do nothing } return $options['format']; } /** * {@inheritdoc} */ public function getLoginUrl($options = []) { $urlGenerator = $this->getUrlGenerator(); // Set redirect_uri to current URL if not defined if (!isset($options['redirect_uri'])) { $options['redirect_uri'] = $urlGenerator->getCurrentUrl(); } return $this->getAuthenticator()->getLoginUrl($urlGenerator, $options); } /** * See docs for LinkedIn::api(). * * @param string $resource * @param array $options * * @return mixed */ public function get($resource, array $options = []) { return $this->api('GET', $resource, $options); } /** * See docs for LinkedIn::api(). * * @param string $resource * @param array $options * * @return mixed */ public function post($resource, array $options = []) { return $this->api('POST', $resource, $options); } /** * {@inheritdoc} */ public function clearStorage() { $this->getAuthenticator()->clearStorage(); return $this; } /** * {@inheritdoc} */ public function hasError() { return GlobalVariableGetter::has('error'); } /** * {@inheritdoc} */ public function getError() { if ($this->hasError()) { return new LoginError(GlobalVariableGetter::get('error'), GlobalVariableGetter::get('error_description')); } } /** * Get the default format to use when sending requests. * * @return string */ protected function getFormat() { return $this->format; } /** * {@inheritdoc} */ public function setFormat($format) { $this->format = $format; return $this; } /** * Get the default data type to be returned as a response. * * @return string */ protected function getResponseDataType() { return $this->responseDataType; } /** * {@inheritdoc} */ public function setResponseDataType($responseDataType) { $this->responseDataType = $responseDataType; return $this; } /** * {@inheritdoc} */ public function getLastResponse() { return $this->lastResponse; } /** * {@inheritdoc} */ public function getAccessToken() { if ($this->accessToken === null) { if (null !== $newAccessToken = $this->getAuthenticator()->fetchNewAccessToken($this->getUrlGenerator())) { $this->setAccessToken($newAccessToken); } } // return the new access token or null if none found return $this->accessToken; } /** * {@inheritdoc} */ public function setAccessToken($accessToken) { if (!($accessToken instanceof AccessToken)) { $accessToken = new AccessToken($accessToken); } $this->accessToken = $accessToken; return $this; } /** * {@inheritdoc} */ public function setUrlGenerator(UrlGeneratorInterface $urlGenerator) { $this->urlGenerator = $urlGenerator; return $this; } /** * @return UrlGeneratorInterface */ protected function getUrlGenerator() { if ($this->urlGenerator === null) { $this->urlGenerator = new UrlGenerator(); } return $this->urlGenerator; } /** * {@inheritdoc} */ public function setStorage(DataStorageInterface $storage) { $this->getAuthenticator()->setStorage($storage); return $this; } /** * {@inheritdoc} */ public function setHttpClient(HttpClient $client) { $this->getRequestManager()->setHttpClient($client); return $this; } /** * {@inheritdoc} */ public function setHttpMessageFactory(MessageFactory $factory) { $this->getRequestManager()->setMessageFactory($factory); return $this; } /** * @return RequestManager */ protected function getRequestManager() { return $this->requestManager; } /** * @return Authenticator */ protected function getAuthenticator() { return $this->authenticator; } } ================================================ FILE: src/LinkedInInterface.php ================================================ */ interface LinkedInInterface { /** * Is the current user authenticated? * * @return bool */ public function isAuthenticated(); /** * Make an API call. Read about what calls that are possible here: https://developer.linkedin.com/docs/rest-api. * * Example: * $linkedIn->api('GET', '/v1/people/~:(id,firstName,lastName,headline)'); * * The options: * - body: the body of the request * - format: the format you are using to send the request * - headers: array with headers to use * - response_data_type: the data type to get back * - query: query parameters to the request * * @param string $method This is the HTTP verb * @param string $resource everything after the domain in the URL. * @param array $options See the readme for option description. * * @return mixed this depends on the response_data_type parameter. */ public function api($method, $resource, array $options = []); /** * Get a login URL where the user can put his/hers LinkedIn credentials and authorize the application. * * The options: * - redirect_uri: the url to go to after a successful login * - scope: comma (or space) separated list of requested extended permissions * * @param array $options Provide custom parameters * * @return string The URL for the login flow */ public function getLoginUrl($options = []); /** * See docs for LinkedIn::api(). * * @param string $resource * @param array $options * * @return mixed */ public function get($resource, array $options = []); /** * See docs for LinkedIn::api(). * * @param string $resource * @param array $options * * @return mixed */ public function post($resource, array $options = []); /** * Clear the data storage. This will forget everything about the user and authentication process. * * @return $this */ public function clearStorage(); /** * If the user has canceled the login we will return with an error. * * @return bool */ public function hasError(); /** * Returns a LoginError or null. * * @return LoginError|null */ public function getError(); /** * Set the default format to use when sending requests. * * @param string $format * * @return $this */ public function setFormat($format); /** * Set the default data type to be returned as a response. * * @param string $responseDataType * * @return $this */ public function setResponseDataType($responseDataType); /** * Get the last response. This will always return a PSR-7 response no matter of the data type used. * * @return ResponseInterface|null */ public function getLastResponse(); /** * Returns an access token. If we do not have one in memory, try to fetch one from a *code* in the $_REQUEST. * * @return AccessToken|null The access token of null if the access token is not found */ public function getAccessToken(); /** * If you have stored a previous access token in a storage (database) you could set it here. After setting an * access token you have to make sure to verify it is still valid by running LinkedIn::isAuthenticated. * * @param string|AccessToken $accessToken * * @return $this */ public function setAccessToken($accessToken); /** * Set a URL generator. * * @param UrlGeneratorInterface $urlGenerator * * @return $this */ public function setUrlGenerator(UrlGeneratorInterface $urlGenerator); /** * Set a data storage. * * @param DataStorageInterface $storage * * @return $this */ public function setStorage(DataStorageInterface $storage); /** * Set a http client. * * @param HttpClient $client * * @return $this */ public function setHttpClient(HttpClient $client); /** * Set a http message factory. * * @param MessageFactory $factory * * @return $this */ public function setHttpMessageFactory(MessageFactory $factory); } ================================================ FILE: src/Storage/BaseDataStorage.php ================================================ clear($key); } } /** * Validate key. Throws an exception if key is not valid. * * @param string $key * * @throws InvalidArgumentException */ protected function validateKey($key) { if (!in_array($key, self::$validKeys)) { throw new InvalidArgumentException('Unsupported key "%s" passed to LinkedIn data storage. Valid keys are: %s', $key, implode(', ', self::$validKeys)); } } /** * Generate an ID to use with the data storage. * * @param $key * * @return string */ protected function getStorageKeyId($key) { return 'linkedIn_'.$key; } } ================================================ FILE: src/Storage/DataStorageInterface.php ================================================ validateKey($key); $name = $this->getStorageKeyId($key); return Session::put($name, $value); } /** * {@inheritdoc} */ public function get($key) { $this->validateKey($key); $name = $this->getStorageKeyId($key); return Session::get($name); } /** * {@inheritdoc} */ public function clear($key) { $this->validateKey($key); $name = $this->getStorageKeyId($key); return Session::forget($name); } } ================================================ FILE: src/Storage/SessionStorage.php ================================================ */ class SessionStorage extends BaseDataStorage { public function __construct() { //start the session if it not already been started if (php_sapi_name() !== 'cli') { if (session_id() === '') { session_start(); } } } /** * {@inheritdoc} */ public function set($key, $value) { $this->validateKey($key); $name = $this->getStorageKeyId($key); $_SESSION[$name] = $value; } /** * {@inheritdoc} */ public function get($key) { $this->validateKey($key); $name = $this->getStorageKeyId($key); return isset($_SESSION[$name]) ? $_SESSION[$name] : null; } /** * {@inheritdoc} */ public function clear($key) { $this->validateKey($key); $name = $this->getStorageKeyId($key); if (isset($_SESSION[$name])) { unset($_SESSION[$name]); } } } ================================================ FILE: tests/AccessTokenTest.php ================================================ assertEquals('', $token); $token->setToken('foobar'); $this->assertEquals('foobar', $token); } public function testConstructor() { $token = new AccessToken('foobar', 10); $this->assertInstanceOf('\DateTime', $token->getExpiresAt()); $this->assertEquals('foobar', $token->getToken()); $token = new AccessToken(); $this->assertNull($token->getExpiresAt()); $this->assertEmpty($token->getToken()); $token = new AccessToken(null, new \DateTime('+2minutes')); $this->assertInstanceOf('\DateTime', $token->getExpiresAt()); } public function testSetExpiresAt() { $token = new AccessToken(); $token->setExpiresAt(new \DateTime('+2minutes')); $this->assertInstanceOf('\DateTime', $token->getExpiresAt()); } } ================================================ FILE: tests/AuthenticatorTest.php ================================================ */ class AuthenticatorTest extends \PHPUnit_Framework_TestCase { const APP_ID = '123456789'; const APP_SECRET = '987654321'; private function getRequestManagerMock() { return m::mock('Happyr\LinkedIn\Http\RequestManager'); } public function testGetLoginUrl() { $expected = 'loginUrl'; $state = 'random'; $params = [ 'response_type' => 'code', 'client_id' => self::APP_ID, 'redirect_uri' => null, 'state' => $state, ]; $storage = $this->getMock('Happyr\LinkedIn\Storage\DataStorageInterface'); $storage->method('get')->with('state')->willReturn($state); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['establishCSRFTokenState', 'getStorage'], [$this->getRequestManagerMock(), self::APP_ID, self::APP_SECRET]); $auth->expects($this->exactly(2))->method('establishCSRFTokenState')->willReturn(null); $auth->method('getStorage')->will($this->returnValue($storage)); $generator = m::mock('Happyr\LinkedIn\Http\LinkedInUrlGeneratorInterface') ->shouldReceive('getUrl')->once()->with('www', 'oauth/v2/authorization', $params)->andReturn($expected) ->getMock(); $this->assertEquals($expected, $auth->getLoginUrl($generator)); /* * Test with a url in the param */ $otherUrl = 'otherUrl'; $scope = ['foo', 'bar', 'baz']; $params = [ 'response_type' => 'code', 'client_id' => self::APP_ID, 'redirect_uri' => $otherUrl, 'state' => $state, 'scope' => 'foo bar baz', ]; $generator = m::mock('Happyr\LinkedIn\Http\LinkedInUrlGeneratorInterface') ->shouldReceive('getUrl')->once()->with('www', 'oauth/v2/authorization', $params)->andReturn($expected) ->getMock(); $this->assertEquals($expected, $auth->getLoginUrl($generator, ['redirect_uri' => $otherUrl, 'scope' => $scope])); } public function testFetchNewAccessToken() { $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator'); $code = 'newCode'; $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface') ->shouldReceive('set')->once()->with('code', $code) ->shouldReceive('set')->once()->with('access_token', 'at') ->getMock(); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getCode', 'getStorage', 'getAccessTokenFromCode'], [], '', false); $auth->expects($this->any())->method('getStorage')->will($this->returnValue($storage)); $auth->expects($this->once())->method('getAccessTokenFromCode')->with($generator, $code)->will($this->returnValue('at')); $auth->expects($this->once())->method('getCode')->will($this->returnValue($code)); $this->assertEquals('at', $auth->fetchNewAccessToken($generator)); } /** * @expectedException \Happyr\LinkedIn\Exception\LinkedInException */ public function testFetchNewAccessTokenFail() { $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator'); $code = 'newCode'; $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface') ->shouldReceive('clearAll')->once() ->getMock(); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getCode', 'getStorage', 'getAccessTokenFromCode'], [], '', false); $auth->expects($this->any())->method('getStorage')->will($this->returnValue($storage)); $auth->expects($this->once())->method('getAccessTokenFromCode')->with($generator, $code)->willThrowException(new LinkedInException()); $auth->expects($this->once())->method('getCode')->will($this->returnValue($code)); $auth->fetchNewAccessToken($generator); } public function testFetchNewAccessTokenNoCode() { $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator'); $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface') ->shouldReceive('get')->with('code')->andReturn('foobar') ->shouldReceive('get')->once()->with('access_token')->andReturn('baz') ->getMock(); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getCode', 'getStorage'], [], '', false); $auth->expects($this->any())->method('getStorage')->will($this->returnValue($storage)); $auth->expects($this->once())->method('getCode'); $this->assertEquals('baz', $auth->fetchNewAccessToken($generator)); } /** * @expectedException \Happyr\LinkedIn\Exception\LinkedInException */ public function testGetAccessTokenFromCodeEmptyString() { $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator'); $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode'); $method->setAccessible(true); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', [], [], '', false); $method->invoke($auth, $generator, ''); } /** * @expectedException \Happyr\LinkedIn\Exception\LinkedInException */ public function testGetAccessTokenFromCodeNull() { $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator'); $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode'); $method->setAccessible(true); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', [], [], '', false); $method->invoke($auth, $generator, null); } /** * @expectedException \Happyr\LinkedIn\Exception\LinkedInException */ public function testGetAccessTokenFromCodeFalse() { $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator'); $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode'); $method->setAccessible(true); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', [], [], '', false); $method->invoke($auth, $generator, false); } public function testGetAccessTokenFromCode() { $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode'); $method->setAccessible(true); $code = 'code'; $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator') ->shouldReceive('getUrl')->with( 'www', 'oauth/v2/accessToken' )->andReturn('url') ->getMock(); $response = ['access_token' => 'foobar', 'expires_in' => 10]; $auth = $this->prepareGetAccessTokenFromCode($code, $response); $token = $method->invoke($auth, $generator, $code); $this->assertEquals('foobar', $token, 'Standard get access token form code'); } /** * @expectedException \Happyr\LinkedIn\Exception\LinkedInException */ public function testGetAccessTokenFromCodeNoTokenInResponse() { $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode'); $method->setAccessible(true); $code = 'code'; $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator') ->shouldReceive('getUrl')->with( 'www', 'oauth/v2/accessToken' )->andReturn('url') ->getMock(); $response = ['foo' => 'bar']; $auth = $this->prepareGetAccessTokenFromCode($code, $response); $this->assertNull($method->invoke($auth, $generator, $code), 'Found array but no access token'); } /** * @expectedException \Happyr\LinkedIn\Exception\LinkedInException */ public function testGetAccessTokenFromCodeEmptyResponse() { $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode'); $method->setAccessible(true); $code = 'code'; $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator') ->shouldReceive('getUrl')->with( 'www', 'oauth/v2/accessToken' )->andReturn('url') ->getMock(); $response = ''; $auth = $this->prepareGetAccessTokenFromCode($code, $response); $this->assertNull($method->invoke($auth, $generator, $code), 'Empty result'); } /** * Default stuff for GetAccessTokenFromCode. * * @param $response * * @return array */ protected function prepareGetAccessTokenFromCode($code, $responseData) { $response = new Response(200, [], json_encode($responseData)); $currentUrl = 'foobar'; $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface') ->shouldReceive('get')->with('redirect_uri')->andReturn($currentUrl) ->getMock(); $requestManager = m::mock('Happyr\LinkedIn\Http\RequestManager') ->shouldReceive('sendRequest')->once()->with('POST', 'url', [ 'Content-Type' => 'application/x-www-form-urlencoded', ], http_build_query([ 'grant_type' => 'authorization_code', 'code' => $code, 'redirect_uri' => $currentUrl, 'client_id' => self::APP_ID, 'client_secret' => self::APP_SECRET, ]))->andReturn($response) ->getMock(); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getStorage'], [$requestManager, self::APP_ID, self::APP_SECRET]); $auth->expects($this->any())->method('getStorage')->will($this->returnValue($storage)); return $auth; } public function testEstablishCSRFTokenState() { $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'establishCSRFTokenState'); $method->setAccessible(true); $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface') ->shouldReceive('get')->with('state')->andReturn(null, 'state') ->shouldReceive('set')->once()->with('state', \Mockery::on(function (&$param) { return !empty($param); })) ->getMock(); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getStorage'], [], '', false); $auth->expects($this->any())->method('getStorage')->will($this->returnValue($storage)); // Make sure we only set the state once $method->invoke($auth); $method->invoke($auth); } public function testGetCodeEmpty() { unset($_REQUEST['code']); unset($_GET['code']); $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getCode'); $method->setAccessible(true); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', [], [], '', false); $this->assertNull($method->invoke($auth)); } public function testGetCode() { $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getCode'); $method->setAccessible(true); $state = 'bazbar'; $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface') ->shouldReceive('clear')->once()->with('state') ->shouldReceive('get')->once()->with('code')->andReturn(null) ->shouldReceive('get')->once()->with('state')->andReturn($state) ->getMock(); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getStorage'], [], '', false); $auth->expects($this->once())->method('getStorage')->will($this->returnValue($storage)); $_REQUEST['code'] = 'foobar'; $_REQUEST['state'] = $state; $this->assertEquals('foobar', $method->invoke($auth)); } /** * @expectedException \Happyr\LinkedIn\Exception\LinkedInException */ public function testGetCodeInvalidCode() { $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getCode'); $method->setAccessible(true); $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface') ->shouldReceive('get')->once()->with('code')->andReturn(null) ->shouldReceive('get')->once()->with('state')->andReturn('bazbar') ->getMock(); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getStorage'], [], '', false); $auth->expects($this->once())->method('getStorage')->will($this->returnValue($storage)); $_REQUEST['code'] = 'foobar'; $_REQUEST['state'] = 'invalid'; $this->assertEquals('foobar', $method->invoke($auth)); } public function testGetCodeUsedCode() { $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getCode'); $method->setAccessible(true); $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface') ->shouldReceive('get')->once()->with('code')->andReturn('foobar') ->getMock(); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getStorage'], [], '', false); $auth->expects($this->once())->method('getStorage')->will($this->returnValue($storage)); $_REQUEST['code'] = 'foobar'; $this->assertEquals(null, $method->invoke($auth)); } public function testStorageAccessors() { $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getStorage'); $method->setAccessible(true); $requestManager = $this->getRequestManagerMock(); $auth = new Authenticator($requestManager, self::APP_ID, self::APP_SECRET); // test default $this->assertInstanceOf('Happyr\LinkedIn\Storage\SessionStorage', $method->invoke($auth)); $object = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface'); $auth->setStorage($object); $this->assertEquals($object, $method->invoke($auth)); } } ================================================ FILE: tests/Exceptions/LoginErrorTest.php ================================================ assertEquals('foo', $error->getName()); $this->assertEquals('bar', $error->getDescription()); } } ================================================ FILE: tests/Http/ResponseConverterTest.php ================================================ assertInstanceOf('Psr\Http\Message\ResponseInterface', $result); $result = ResponseConverter::convert($response, 'json', 'stream'); $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $result); $result = ResponseConverter::convert($response, 'json', 'string'); $this->assertTrue(is_string($result)); $this->assertEquals($body, $result); $result = ResponseConverter::convert($response, 'json', 'array'); $this->assertTrue(is_array($result)); $body = ' foo bar '; $response = new Response(200, [], $body); $result = ResponseConverter::convert($response, 'xml', 'simple_xml'); $this->assertInstanceOf('\SimpleXMLElement', $result); } /** * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException */ public function testConvertJsonToSimpleXml() { $body = '{"foo":"bar"}'; $response = new Response(200, [], $body); ResponseConverter::convert($response, 'json', 'simple_xml'); } /** * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException */ public function testConvertXmlToArray() { $body = ' foo bar '; $response = new Response(200, [], $body); ResponseConverter::convert($response, 'xml', 'array'); } /** * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException */ public function testConvertJsonToFoobar() { $body = '{"foo":"bar"}'; $response = new Response(200, [], $body); ResponseConverter::convert($response, 'json', 'foobar'); } public function testConvertToSimpleXml() { $body = ' foo bar '; $response = new Response(200, [], $body); $result = ResponseConverter::convertToSimpleXml($response); $this->assertInstanceOf('\SimpleXMLElement', $result); $this->assertEquals('foo', $result->firstname); } /** * @expectedException \Happyr\LinkedIn\Exception\LinkedInTransferException */ public function testConvertToSimpleXmlError() { $body = '{Foo: bar}'; $response = new Response(200, [], $body); $result = ResponseConverter::convertToSimpleXml($response); $this->assertInstanceOf('\SimpleXMLElement', $result); $this->assertEquals('foo', $result->firstname); } } ================================================ FILE: tests/Http/UrlGeneratorTest.php ================================================ assertEquals($expected, $gen->dropLinkedInParams($test)); $test = 'code=foobar&baz=foo'; $expected = '?baz=foo'; $this->assertEquals($expected, $gen->dropLinkedInParams($test)); $test = 'foo=bar&code=foobar'; $expected = '?foo=bar'; $this->assertEquals($expected, $gen->dropLinkedInParams($test)); $test = 'code=foobar'; $expected = ''; $this->assertEquals($expected, $gen->dropLinkedInParams($test)); $test = ''; $expected = ''; $this->assertEquals($expected, $gen->dropLinkedInParams($test)); /* ----------------- */ $test = 'foo=bar&code='; $expected = '?foo=bar'; $this->assertEquals($expected, $gen->dropLinkedInParams($test)); $test = 'code='; $expected = ''; $this->assertEquals($expected, $gen->dropLinkedInParams($test)); $test = 'foo=bar&code'; $expected = '?foo=bar'; $this->assertEquals($expected, $gen->dropLinkedInParams($test)); $test = 'code'; $expected = ''; $this->assertEquals($expected, $gen->dropLinkedInParams($test)); } public function testGetUrl() { $gen = new DummyUrlGenerator(); $expected = 'https://api.linkedin.com/?bar=baz'; $this->assertEquals($expected, $gen->getUrl('api', '', ['bar' => 'baz']), 'No path'); $expected = 'https://api.linkedin.com/foobar'; $this->assertEquals($expected, $gen->getUrl('api', 'foobar'), 'Path does not begin with forward slash'); $this->assertEquals($expected, $gen->getUrl('api', '/foobar'), 'Path begins with forward slash'); $expected = 'https://api.linkedin.com/foobar?bar=baz'; $this->assertEquals($expected, $gen->getUrl('api', 'foobar', ['bar' => 'baz']), 'One parameter'); $expected = 'https://api.linkedin.com/foobar?bar=baz&a=b&c=d'; $this->assertEquals($expected, $gen->getUrl('api', 'foobar', ['bar' => 'baz', 'a' => 'b', 'c' => 'd']), 'Many parameters'); $expected = 'https://api.linkedin.com/foobar?bar=baz%20a%20b'; $notExpected = 'https://api.linkedin.com/foobar?bar=baz+a+b'; $this->assertEquals($expected, $gen->getUrl('api', 'foobar', ['bar' => 'baz a b']), 'Use of PHP_QUERY_RFC3986'); $this->assertNotEquals($notExpected, $gen->getUrl('api', 'foobar', ['bar' => 'baz a b']), 'Dont use PHP_QUERY_RFC1738'); } public function testGetUrlWithParams() { $gen = new UrlGenerator(); $expected = 'https://api.linkedin.com/endpoint?bar=baz&format=json'; $this->assertEquals($expected, $gen->getUrl('api', 'endpoint?bar=baz', ['format' => 'json'])); $expected = 'https://api.linkedin.com/endpoint?bar=baz&bar=baz'; $this->assertEquals($expected, $gen->getUrl('api', 'endpoint?bar=baz', ['bar' => 'baz'])); } public function testGetCurrentURL() { $gen = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator', ['getHttpProtocol', 'getHttpHost', 'dropLinkedInParams'], []); $gen->expects($this->any())->method('getHttpProtocol')->will($this->returnValue('http')); $gen->expects($this->any())->method('getHttpHost')->will($this->returnValue('www.test.com')); $gen->expects($this->any())->method('dropLinkedInParams')->will($this->returnCallback(function ($arg) { return empty($arg) ? '' : '?'.$arg; })); // fake the HPHP $_SERVER globals $_SERVER['REQUEST_URI'] = '/unit-tests.php?one=one&two=two&three=three'; $this->assertEquals( 'http://www.test.com/unit-tests.php?one=one&two=two&three=three', $gen->getCurrentUrl(), 'getCurrentUrl function is changing the current URL'); // ensure structure of valueless GET params is retained (sometimes // an = sign was present, and sometimes it was not) // first test when equal signs are present $_SERVER['REQUEST_URI'] = '/unit-tests.php?one=&two=&three='; $this->assertEquals( 'http://www.test.com/unit-tests.php?one=&two=&three=', $gen->getCurrentUrl(), 'getCurrentUrl function is changing the current URL'); // now confirm that $_SERVER['REQUEST_URI'] = '/unit-tests.php?one&two&three'; $this->assertEquals( 'http://www.test.com/unit-tests.php?one&two&three', $gen->getCurrentUrl(), 'getCurrentUrl function is changing the current URL' ); } public function testGetCurrentURLPort80() { $gen = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator', ['getHttpProtocol', 'getHttpHost', 'dropLinkedInParams'], []); $gen->expects($this->any())->method('getHttpProtocol')->will($this->returnValue('http')); $gen->expects($this->any())->method('getHttpHost')->will($this->returnValue('www.test.com:80')); $gen->expects($this->any())->method('dropLinkedInParams')->will($this->returnCallback(function ($arg) { return empty($arg) ? '' : '?'.$arg; })); //test port 80 $_SERVER['REQUEST_URI'] = '/foobar.php'; $this->assertEquals( 'http://www.test.com/foobar.php', $gen->getCurrentUrl(), 'port 80 should not be shown' ); } public function testGetCurrentURLPort8080() { $gen = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator', ['getHttpProtocol', 'getHttpHost', 'dropLinkedInParams'], []); $gen->expects($this->any())->method('getHttpProtocol')->will($this->returnValue('http')); $gen->expects($this->any())->method('getHttpHost')->will($this->returnValue('www.test.com:8080')); $gen->expects($this->any())->method('dropLinkedInParams')->will($this->returnCallback(function ($arg) { return empty($arg) ? '' : '?'.$arg; })); //test non default port 8080 $_SERVER['REQUEST_URI'] = '/foobar.php'; $this->assertEquals( 'http://www.test.com:8080/foobar.php', $gen->getCurrentUrl(), 'port 80 should not be shown' ); } public function testHttpHost() { $real = 'foo.com'; $_SERVER['HTTP_HOST'] = $real; $_SERVER['HTTP_X_FORWARDED_HOST'] = 'evil.com'; $gen = new DummyUrlGenerator(); $this->assertEquals($real, $gen->GetHttpHost()); } public function testHttpProtocolApache() { $_SERVER['HTTPS'] = 'on'; $gen = new DummyUrlGenerator(); $this->assertEquals('https', $gen->GetHttpProtocol()); } public function testHttpProtocolNginx() { $_SERVER['SERVER_PORT'] = '443'; $gen = new DummyUrlGenerator(); $this->assertEquals('https', $gen->GetHttpProtocol()); } public function testHttpHostForwarded() { $real = 'foo.com'; $_SERVER['HTTP_HOST'] = 'localhost'; $_SERVER['HTTP_X_FORWARDED_HOST'] = $real; $gen = new DummyUrlGenerator(); $gen->setTrustForwarded(true); $this->assertEquals($real, $gen->GetHttpHost()); } public function testHttpProtocolForwarded() { $_SERVER['HTTPS'] = 'on'; $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http'; $gen = new DummyUrlGenerator(); $gen->setTrustForwarded(true); $this->assertEquals('http', $gen->GetHttpProtocol()); } public function testHttpProtocolForwardedSecure() { $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https'; $gen = new DummyUrlGenerator(); $this->assertEquals('http', $gen->GetHttpProtocol()); $gen->setTrustForwarded(true); $this->assertEquals('https', $gen->GetHttpProtocol()); } protected function tearDown() { unset($_SERVER['HTTPS']); unset($_SERVER['HTTP_X_FORWARDED_PROTO']); $_SERVER['HTTP_HOST'] = 'localhost'; unset($_SERVER['HTTP_X_FORWARDED_HOST']); $_SERVER['SERVER_PORT'] = '80'; $_SERVER['REQUEST_URI'] = ''; } } class DummyUrlGenerator extends UrlGenerator { public function getHttpHost() { return parent::getHttpHost(); } public function getHttpProtocol() { return parent::getHttpProtocol(); } public function dropLinkedInParams($query) { return parent::dropLinkedInParams($query); } } ================================================ FILE: tests/LinkedInTest.php ================================================ */ class LinkedInTest extends \PHPUnit_Framework_TestCase { const APP_ID = '123456789'; const APP_SECRET = '987654321'; public function testApi() { $resource = 'resource'; $token = 'token'; $urlParams = ['url' => 'foo']; $postParams = ['post' => 'bar']; $method = 'GET'; $expected = ['foobar' => 'test']; $response = new Response(200, [], json_encode($expected)); $url = 'http://example.com/test'; $headers = ['Authorization' => 'Bearer '.$token, 'Content-Type' => 'application/json', 'x-li-format' => 'json']; $generator = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator', ['getUrl']); $generator->expects($this->once())->method('getUrl')->with( $this->equalTo('api'), $this->equalTo($resource), $this->equalTo([ 'url' => 'foo', 'format' => 'json', ])) ->willReturn($url); $requestManager = $this->getMock('Happyr\LinkedIn\Http\RequestManager', ['sendRequest']); $requestManager->expects($this->once())->method('sendRequest')->with( $this->equalTo($method), $this->equalTo($url), $this->equalTo($headers), $this->equalTo(json_encode($postParams))) ->willReturn($response); $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['getAccessToken', 'getUrlGenerator', 'getRequestManager'], [self::APP_ID, self::APP_SECRET]); $linkedIn->expects($this->once())->method('getAccessToken')->willReturn($token); $linkedIn->expects($this->once())->method('getUrlGenerator')->willReturn($generator); $linkedIn->expects($this->once())->method('getRequestManager')->willReturn($requestManager); $result = $linkedIn->api($method, $resource, ['query' => $urlParams, 'json' => $postParams]); $this->assertEquals($expected, $result); } public function testIsAuthenticated() { $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['getAccessToken'], [self::APP_ID, self::APP_SECRET]); $linkedIn->expects($this->once())->method('getAccessToken')->willReturn(null); $this->assertFalse($linkedIn->isAuthenticated()); $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['api', 'getAccessToken'], [self::APP_ID, self::APP_SECRET]); $linkedIn->expects($this->once())->method('getAccessToken')->willReturn('token'); $linkedIn->expects($this->once())->method('api')->willReturn(['id' => 4711]); $this->assertTrue($linkedIn->isAuthenticated()); $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['api', 'getAccessToken'], [self::APP_ID, self::APP_SECRET]); $linkedIn->expects($this->once())->method('getAccessToken')->willReturn('token'); $linkedIn->expects($this->once())->method('api')->willReturn(['foobar' => 4711]); $this->assertFalse($linkedIn->isAuthenticated()); } /** * Test a call to getAccessToken when there is no token. */ public function testAccessTokenAccessors() { $token = 'token'; $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['fetchNewAccessToken'], [], '', false); $auth->expects($this->once())->method('fetchNewAccessToken')->will($this->returnValue($token)); $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['getAuthenticator'], [], '', false); $linkedIn->expects($this->once())->method('getAuthenticator')->willReturn($auth); // Make sure we go to the authenticator only once $this->assertEquals($token, $linkedIn->getAccessToken()); $this->assertEquals($token, $linkedIn->getAccessToken()); } public function testGeneratorAccessors() { $get = new \ReflectionMethod('Happyr\LinkedIn\LinkedIn', 'getUrlGenerator'); $get->setAccessible(true); $linkedIn = new LinkedIn(self::APP_ID, self::APP_SECRET); // test default $this->assertInstanceOf('Happyr\LinkedIn\Http\UrlGenerator', $get->invoke($linkedIn)); $object = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator'); $linkedIn->setUrlGenerator($object); $this->assertEquals($object, $get->invoke($linkedIn)); } public function testHasError() { $linkedIn = new LinkedIn(self::APP_ID, self::APP_SECRET); unset($_GET['error']); $this->assertFalse($linkedIn->hasError()); $_GET['error'] = 'foobar'; $this->assertTrue($linkedIn->hasError()); } public function testGetError() { $linkedIn = new LinkedIn(self::APP_ID, self::APP_SECRET); unset($_GET['error']); unset($_GET['error_description']); $this->assertNull($linkedIn->getError()); $_GET['error'] = 'foo'; $_GET['error_description'] = 'bar'; $this->assertEquals('foo', $linkedIn->getError()->getName()); $this->assertEquals('bar', $linkedIn->getError()->getDescription()); } public function testGetErrorWithMissingDescription() { $linkedIn = new LinkedIn(self::APP_ID, self::APP_SECRET); unset($_GET['error']); unset($_GET['error_description']); $_GET['error'] = 'foo'; $this->assertEquals('foo', $linkedIn->getError()->getName()); $this->assertNull($linkedIn->getError()->getDescription()); } public function testFormatAccessors() { $get = new \ReflectionMethod('Happyr\LinkedIn\LinkedIn', 'getFormat'); $get->setAccessible(true); $linkedIn = new LinkedIn(self::APP_ID, self::APP_SECRET); //test default $this->assertEquals('json', $get->invoke($linkedIn)); $format = 'foo'; $linkedIn->setFormat($format); $this->assertEquals($format, $get->invoke($linkedIn)); } public function testLoginUrl() { $currentUrl = 'currentUrl'; $loginUrl = 'result'; $generator = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator', ['getCurrentUrl']); $generator->expects($this->once())->method('getCurrentUrl')->willReturn($currentUrl); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getLoginUrl'], [], '', false); $auth->expects($this->once())->method('getLoginUrl') ->with($generator, ['redirect_uri' => $currentUrl]) ->will($this->returnValue($loginUrl)); $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['getAuthenticator', 'getUrlGenerator'], [], '', false); $linkedIn->expects($this->once())->method('getAuthenticator')->willReturn($auth); $linkedIn->expects($this->once())->method('getUrlGenerator')->willReturn($generator); $linkedIn->getLoginUrl(); } public function testLoginUrlWithParameter() { $loginUrl = 'result'; $otherUrl = 'otherUrl'; $generator = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator'); $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getLoginUrl'], [], '', false); $auth->expects($this->once())->method('getLoginUrl') ->with($generator, ['redirect_uri' => $otherUrl]) ->will($this->returnValue($loginUrl)); $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['getAuthenticator', 'getUrlGenerator'], [], '', false); $linkedIn->expects($this->once())->method('getAuthenticator')->willReturn($auth); $linkedIn->expects($this->once())->method('getUrlGenerator')->willReturn($generator); $linkedIn->getLoginUrl(['redirect_uri' => $otherUrl]); } } ================================================ FILE: tests/Storage/IlluminateSessionStorageTest.php ================================================ storage = new IlluminateSessionStorage(); } public function testSet() { Session::shouldReceive('put')->once()->with($this->prefix.'code', 'foobar'); $this->storage->set('code', 'foobar'); } /** * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException */ public function testSetFail() { $this->storage->set('foobar', 'baz'); } public function testGet() { $expected = 'foobar'; Session::shouldReceive('get')->once()->with($this->prefix.'code')->andReturn($expected); $result = $this->storage->get('code'); $this->assertEquals($expected, $result); Session::shouldReceive('get')->once()->with($this->prefix.'state')->andReturn(null); $result = $this->storage->get('state'); $this->assertNull($result); } public function testClear() { Session::shouldReceive('forget')->once()->with($this->prefix.'code')->andReturn(true); $this->storage->clear('code'); } /** * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException */ public function testClearFail() { $this->storage->clear('foobar'); } } ================================================ FILE: tests/Storage/SessionStorageTest.php ================================================ storage = new SessionStorage(); } public function testSet() { $this->storage->set('code', 'foobar'); $this->assertEquals($_SESSION[$this->prefix.'code'], 'foobar'); } /** * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException */ public function testSetFail() { $this->storage->set('foobar', 'baz'); } public function testGet() { unset($_SESSION[$this->prefix.'state']); $result = $this->storage->get('state'); $this->assertNull($result); $expected = 'foobar'; $_SESSION[$this->prefix.'code'] = $expected; $result = $this->storage->get('code'); $this->assertEquals($expected, $result); } public function testClear() { $_SESSION[$this->prefix.'code'] = 'foobar'; $this->storage->clear('code'); $this->assertFalse(isset($_SESSION[$this->prefix.'code'])); } /** * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException */ public function testClearFail() { $this->storage->clear('foobar'); } public function testClearAll() { $validKeys = SessionStorage::$validKeys; $storage = m::mock('Happyr\LinkedIn\Storage\SessionStorage[clear]') ->shouldReceive('clear')->times(count($validKeys)) ->with(m::on(function ($arg) use ($validKeys) { return in_array($arg, $validKeys); })) ->getMock(); $storage->clearAll(); } }