├── .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
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | | Q | A
2 | | ----------------------- | ---
3 | | Bug? | no|yes
4 | | New Feature? | no|yes
5 | | Are you using composer? | no|yes
6 | | Version | Specific version or SHA of a commit
7 |
8 |
9 | #### Actual Behavior
10 |
11 | What is the actual behavior?
12 |
13 |
14 | #### Expected Behavior
15 |
16 | What is the behavior you expect?
17 |
18 |
19 | #### Steps to Reproduce
20 |
21 | What are the steps to reproduce this bug? Please add code examples,
22 | screenshots or links to GitHub repositories that reproduce the problem.
23 |
24 |
25 | #### Possible Solutions
26 |
27 | If you have already ideas how to solve the issue, add them here.
28 | (remove this section if not needed)
29 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | | Q | A
2 | | --------------- | ---
3 | | Bug fix? | no|yes
4 | | New feature? | no|yes
5 | | BC breaks? | no|yes
6 | | Deprecations? | no|yes
7 | | Related tickets | fixes #X, partially #Y, mentioned in #Z
8 | | License | MIT
9 |
10 |
11 | #### What's in this PR?
12 |
13 | Explain what the changes in this PR do.
14 |
15 |
16 | #### Why?
17 |
18 | Which problem does the PR fix? (remove this section if you linked an issue above)
19 |
20 |
21 | #### Example Usage
22 |
23 | ``` php
24 | // If you added new features, show examples of how to use them here
25 | // (remove this section if not a new feature)
26 |
27 | $foo = new Foo();
28 |
29 | // Now we can do
30 | $foo->doSomething();
31 | ```
32 |
33 |
34 | #### Checklist
35 |
36 | - [ ] Updated CHANGELOG.md to describe BC breaks / deprecations | new feature | bugfix
37 | - [ ] Documentation pull request created (if not simply a bugfix)
38 |
39 |
40 | #### To Do
41 |
42 | - [ ] If the PR is not complete but you want to discuss the approach, list what remains to be done here
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 | composer.lock
3 | index.php
4 | phpunit.xml
5 | /vendor/
6 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | filter:
2 | paths: [src/*]
3 | checks:
4 | php:
5 | code_rating: true
6 | duplication: true
7 | tools:
8 | external_code_coverage: true
9 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: symfony
2 |
3 | finder:
4 | path:
5 | - "src"
6 | - "tests"
7 |
8 | enabled:
9 | - short_array_syntax
10 |
11 | disabled:
12 | - phpdoc_annotation_without_dot
13 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | sudo: false
3 |
4 | cache:
5 | directories:
6 | - $HOME/.composer/cache/files
7 |
8 | php:
9 | - 5.5
10 | - 5.6
11 | - 7.0
12 | - 7.1
13 | - hhvm
14 |
15 | env:
16 | global:
17 | - TEST_COMMAND="composer test"
18 |
19 | matrix:
20 | fast_finish: true
21 | include:
22 | - php: 5.5
23 | env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" COVERAGE=true TEST_COMMAND="composer test-ci"
24 |
25 | before_install:
26 | - composer self-update
27 |
28 | install:
29 | - travis_retry composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction
30 |
31 | script:
32 | - $TEST_COMMAND
33 |
34 | after_success:
35 | - if [[ $COVERAGE = true ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi
36 | - if [[ $COVERAGE = true ]]; then php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml; fi
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Tobias Nyholm
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # LinkedIn API client in PHP
2 |
3 | [](https://github.com/Happyr/LinkedIn-API-client/releases)
4 | [](LICENSE)
5 | [](https://travis-ci.org/Happyr/LinkedIn-API-client)
6 | [](https://insight.sensiolabs.com/projects/44c425af-90f6-4c25-b789-4ece28b01a2b)
7 | [](https://scrutinizer-ci.com/g/Happyr/LinkedIn-API-client)
8 | [](https://scrutinizer-ci.com/g/Happyr/LinkedIn-API-client)
9 | [](https://packagist.org/packages/happyr/linkedin-api-client)
10 |
11 |
12 | A PHP library to handle authentication and communication with LinkedIn API. The library/SDK helps you to get an access
13 | token and when authenticated it helps you to send API requests. You will not get *everything* for free though... You
14 | have to read the [LinkedIn documentation][api-doc-core] to understand how you should query the API.
15 |
16 | To get an overview what this library actually is doing for you. Take a look at the authentication page from
17 | the [API docs][api-doc-authentication].
18 |
19 | ## Features
20 |
21 | Here is a list of features that might convince you to choose this LinkedIn client over some of our competitors'.
22 |
23 | * Flexible and easy to extend
24 | * Developed with modern PHP standards
25 | * Not developed for a specific framework.
26 | * Handles the authentication process
27 | * Respects the CSRF protection
28 |
29 | ## Installation
30 |
31 | **TL;DR**
32 | ```bash
33 | composer require php-http/curl-client guzzlehttp/psr7 php-http/message happyr/linkedin-api-client
34 | ```
35 |
36 | This library does not have a dependency on Guzzle or any other library that sends HTTP requests. We use the awesome
37 | HTTPlug to achieve the decoupling. We want you to choose what library to use for sending HTTP requests. Consult this list
38 | of packages that support [php-http/client-implementation](https://packagist.org/providers/php-http/client-implementation)
39 | find clients to use. For more information about virtual packages please refer to
40 | [HTTPlug](http://docs.php-http.org/en/latest/httplug/users.html). Example:
41 |
42 | ```bash
43 | composer require php-http/guzzle6-adapter
44 | ```
45 |
46 | You do also need to install a PSR-7 implementation and a factory to create PSR-7 messages (PSR-17 whenever that is
47 | released). You could use Guzzles PSR-7 implementation and factories from php-http:
48 |
49 | ```bash
50 | composer require guzzlehttp/psr7 php-http/message
51 | ```
52 |
53 | Now you may install the library by running the following:
54 |
55 | ```bash
56 | composer require happyr/linkedin-api-client
57 | ```
58 |
59 | If you are updating form a previous version make sure to read [the upgrade documentation](Upgrade.md).
60 |
61 | ### Finding the HTTP client (optional)
62 |
63 | The LinkedIn client need to know what library you are using to send HTTP messages. You could provide an instance of
64 | HttpClient and MessageFactory or you could fallback on auto discovery. Below is an example on where you provide a Guzzle6
65 | instance.
66 |
67 | ```php
68 | $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret');
69 | $linkedIn->setHttpClient(new \Http\Adapter\Guzzle6\Client());
70 | $linkedIn->setHttpMessageFactory(new Http\Message\MessageFactory\GuzzleMessageFactory());
71 |
72 | ```
73 |
74 | ## Usage
75 |
76 | In order to use this API client (or any other LinkedIn clients) you have to [register your application][register-app]
77 | with LinkedIn to receive an API key. Once you've registered your LinkedIn app, you will be provided with
78 | an *API Key* and *Secret Key*.
79 |
80 | ### LinkedIn login
81 |
82 | This example below is showing how to login with LinkedIn.
83 |
84 | ```php
85 | isAuthenticated()) {
100 | //we know that the user is authenticated now. Start query the API
101 | $user=$linkedIn->get('v1/people/~:(firstName,lastName)');
102 | echo "Welcome ".$user['firstName'];
103 |
104 | exit();
105 | } elseif ($linkedIn->hasError()) {
106 | echo "User canceled the login.";
107 | exit();
108 | }
109 |
110 | //if not authenticated
111 | $url = $linkedIn->getLoginUrl();
112 | echo "Login with LinkedIn";
113 |
114 | ```
115 |
116 | ### How to post on LinkedIn wall
117 |
118 | The example below shows how you can post on a users wall. The access token is fetched from the database.
119 |
120 | ```php
121 | $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret');
122 | $linkedIn->setAccessToken('access_token_from_db');
123 |
124 | $options = array('json'=>
125 | array(
126 | 'comment' => 'Im testing Happyr LinkedIn client! https://github.com/Happyr/LinkedIn-API-client',
127 | 'visibility' => array(
128 | 'code' => 'anyone'
129 | )
130 | )
131 | );
132 |
133 | $result = $linkedIn->post('v1/people/~/shares', $options);
134 |
135 | var_dump($result);
136 |
137 | // Prints:
138 | // array (size=2)
139 | // 'updateKey' => string 'UPDATE-01234567-0123456789012345678' (length=35)
140 | // 'updateUrl' => string 'https://www.linkedin.com/updates?discuss=&scope=01234567&stype=M&topic=0123456789012345678&type=U&a=mVKU' (length=104)
141 |
142 | ```
143 |
144 | You may of course do the same in xml. Use the following options array.
145 | ```php
146 | $options = array(
147 | 'format' => 'xml',
148 | 'body' => '
149 | Im testing Happyr LinkedIn client! https://github.com/Happyr/LinkedIn-API-client
150 |
151 | anyone
152 |
153 | ');
154 | ```
155 |
156 | ## Configuration
157 |
158 | ### The api options
159 |
160 | The third parameter of `LinkedIn::api` is an array with options. Below is a table of array keys that you may use.
161 |
162 | | Option name | Description
163 | | ----------- | -----------
164 | | body | The body of a HTTP request. Put your xml string here.
165 | | format | Set this to 'json', 'xml' or 'simple_xml' to override the default value.
166 | | headers | This is HTTP headers to the request
167 | | 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.
168 | | response_data_type | To override the response format for one request
169 | | query | This is an array with query parameters
170 |
171 |
172 |
173 | ### Changing request format
174 |
175 | The default format when communicating with LinkedIn API is json. You can let the API do `json_encode` for you.
176 | The following code shows you how.
177 |
178 | ```php
179 | $body = array(
180 | 'comment' => 'Im testing Happyr LinkedIn client! https://github.com/Happyr/LinkedIn-API-client',
181 | 'visibility' => array('code' => 'anyone')
182 | );
183 |
184 | $linkedIn->post('v1/people/~/shares', array('json'=>$body));
185 | $linkedIn->post('v1/people/~/shares', array('body'=>json_encode($body)));
186 | ```
187 |
188 | When using `array('json'=>$body)` as option the format will always be `json`. You can change the request format in three ways.
189 |
190 | ```php
191 | // By constructor argument
192 | $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret', 'xml');
193 |
194 | // By setter
195 | $linkedIn->setFormat('xml');
196 |
197 | // Set format for just one request
198 | $linkedIn->post('v1/people/~/shares', array('format'=>'xml', 'body'=>$body));
199 | ```
200 |
201 |
202 | ### Understanding response data type
203 |
204 | The data type returned from `LinkedIn::api` can be configured. You may use the forth construtor argument, the
205 | `LinkedIn::setResponseDataType` or as an option for `LinkedIn::api`
206 |
207 | ```php
208 | // By constructor argument
209 | $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret', 'json', 'array');
210 |
211 | // By setter
212 | $linkedIn->setResponseDataType('simple_xml');
213 |
214 | // Set format for just one request
215 | $linkedIn->get('v1/people/~:(firstName,lastName)', array('response_data_type'=>'psr7'));
216 |
217 | ```
218 |
219 | Below is a table that specifies what the possible return data types are when you call `LinkedIn::api`.
220 |
221 | | Type | Description
222 | | ------ | ------------
223 | | array | An assosiative array. This can only be used with the `json` format.
224 | | simple_xml | A SimpleXMLElement. See [PHP manual](http://php.net/manual/en/class.simplexmlelement.php). This can only be used with the `xml` format.
225 | | psr7 | A PSR7 response.
226 | | stream | A file stream.
227 | | string | A plain old string.
228 |
229 |
230 | ### Use different Session classes
231 |
232 | You might want to use an other storage than the default `SessionStorage`. If you are using Laravel
233 | you are more likely to inject the `IlluminateSessionStorage`.
234 | ```php
235 | $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret');
236 | $linkedIn->setStorage(new IlluminateSessionStorage());
237 | ```
238 |
239 | You can inject any class implementing `DataStorageInterface`. You can also inject different `UrlGenerator` classes.
240 |
241 | ### Using different scopes
242 |
243 | If you want to define special scopes when you authenticate the user you should specify them when you are generating the
244 | login url. If you don't specify scopes LinkedIn will use the default scopes that you have configured for the app.
245 |
246 | ```php
247 | $scope = 'r_fullprofile,r_emailaddress,w_share';
248 | //or
249 | $scope = array('rw_groups', 'r_contactinfo', 'r_fullprofile', 'w_messages');
250 |
251 | $url = $linkedIn->getLoginUrl(array('scope'=>$scope));
252 | echo "Login with LinkedIn";
253 | ```
254 |
255 | ## Framework integration
256 |
257 | If you want an easier integration with a framwork you may want to check out these repositories:
258 |
259 | * [HappyrLinkedInBundle](https://github.com/Happyr/LinkedInBundle) for Symfony
260 | * [Laravel-Linkedin by mauri870](https://github.com/artesaos/laravel-linkedin) for Laravel 5
261 |
262 |
263 | [register-app]: https://www.linkedin.com/secure/developer
264 | [linkedin-code-samples]: https://developer.linkedin.com/documents/code-samples
265 | [api-doc-authentication]: https://developer.linkedin.com/documents/authentication
266 | [api-doc-core]: https://developer.linkedin.com/core-concepts
267 |
--------------------------------------------------------------------------------
/Upgrade.md:
--------------------------------------------------------------------------------
1 | # Upgrade
2 |
3 | This document explains how you upgrade from one version to another.
4 |
5 | ## Upgrade from 0.7.2 to 1.0
6 |
7 | ### Changes
8 |
9 | * We do not longer require `php-http/message`. You have to make sure to put that in your own composer.json.
10 |
11 | ## Upgrade from 0.7.1 to 0.7.2
12 |
13 | ### Changes
14 |
15 | * Using `php-http/discovery:1.0`
16 | * Code style changes.
17 |
18 | ## Upgrade from 0.7.0 to 0.7.1
19 |
20 | ### Changes
21 |
22 | * Using `php-http/discovery:0.9` which makes Puli optional
23 | * Using new URL's to LinkedIn API so users are provided with the new authentication UX. (Thanks to @mbarwick83)
24 |
25 | ## Upgrade from 0.6 to 0.7
26 |
27 | ### Changes
28 |
29 | * Introduced PHP-HTTP and PSR-7 messages
30 | * Added constructor argument for responseDataType
31 | * Added setResponseDataType()
32 | * Moved authentication functions to `Authenticator` class
33 |
34 | To make sure you can upgrade you need to install a HTTP adapter.
35 |
36 | ```bash
37 | php composer.phar require php-http/guzzle6-adapter
38 | ```
39 |
40 | ### BC breaks
41 |
42 | * Removed `LinkedIn::setRequest` in favor of `LinkedIn::setHttpAdapter`
43 | * Removed `LinkedIn::getAppSecret` and `LinkedIn::getAppId`
44 | * Removed `LinkedIn::getUser`
45 | * Removed `LinkedInApiException` in favor of `LinkedInException`, `InvalidArgumentException` and `LinkedInTransferException`
46 | * Removed `LinkedIn::getLastHeaders` in favor of `LinkedIn::getLastResponse`
47 | * Made the public functions `LinkedIn::getResponseDataType` and `LinkedIn::getFormat` protected
48 |
49 | ## Upgrade from 0.5 to 0.6
50 |
51 | ### Changes
52 |
53 | * When exchanging the code for an access token we are now using the post body instead of query parameters
54 | * Better error handling when exchange from code to access token fails
55 |
56 | ### BC breaks
57 |
58 | There are a few minor BC breaks. We removed the functions below:
59 |
60 | * `LinkedIn::getUserId`, use `LinkedIn::getUser` instead
61 | * `AccessToken::constructFromJson`, Use the constructor instead.
62 |
63 | ## Upgrade from 0.4 to 0.5
64 |
65 | ### Changed signature of `LinkedIn::api`
66 |
67 | The signature of `LinkedIn::api` has changed to be more easy to work with.
68 | ```php
69 | // Version 0.4
70 | public function api($resource, array $urlParams=array(), $method='GET', $postParams=array())
71 |
72 | // Version 0.5
73 | public function api($method, $resource, array $options=array())
74 | ```
75 |
76 | This means that you have to modify your calls to:
77 | ```php
78 | // Version 0.5
79 | $options = array('query'=>$urlParams, 'body'=>$postParams);
80 | $linkedIn->api('POST', $resource, $options)
81 | ```
82 | See the Readme about more options to the API function.
83 |
84 | ### Must inject IlluminateSessionStorage
85 |
86 | We have removed the protected `LinkedIn::init` function. That means if you were using `IlluminateSessionStorage` you have
87 | to make a minor adjustment to your code.
88 |
89 | ```php
90 | // Version 0.4
91 | $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret');
92 |
93 | // Version 0.5
94 | $linkedIn=new Happyr\LinkedIn\LinkedIn('app_id', 'app_secret');
95 | $linkedIn->setStorage(new IlluminateSessionStorage());
96 | ```
97 |
98 | If you don't know about `IlluminateSessionStorage` you are probably good ignoring this.
99 |
100 | ### Default format
101 |
102 | The default format when communicating with LinkedIn API is changed to json.
103 |
104 | ### Updated RequestInterface
105 |
106 | The `RequestInterface::send` was updated with a new signature. We did also introduce `RequestInterface::getHeadersFromLastResponse`.
107 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "happyr/linkedin-api-client",
3 | "type": "library",
4 | "description": "LinkedIn API client. Handles OAuth, CSRF protection. Easy to implement and extend. This is a standalone library for any composer project.",
5 | "keywords": ["LinkedIn", "OAuth", "API", "Client", "SDK"],
6 | "homepage": "http://developer.happyr.com/libraries/linkedin-php-client",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Tobias Nyholm",
11 | "email": "tobias@happyr.com"
12 | }
13 | ],
14 | "require": {
15 | "php": "^5.5 || ^7.0",
16 | "php-http/client-implementation": "^1.0",
17 | "php-http/httplug": "^1.0",
18 | "php-http/message-factory": "^1.0",
19 | "php-http/discovery": "^1.0"
20 | },
21 | "require-dev": {
22 | "phpunit/phpunit": "^4.5 || ^5.0",
23 | "php-http/guzzle5-adapter": "^1.0",
24 | "guzzlehttp/psr7": "^1.2",
25 | "mockery/mockery": "^0.9",
26 | "illuminate/support": "^5.0"
27 | },
28 | "autoload": {
29 | "psr-4": { "Happyr\\LinkedIn\\": "src/" }
30 | },
31 | "scripts": {
32 | "test": "vendor/bin/phpunit",
33 | "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml"
34 | },
35 | "extra": {
36 | "branch-alias": {
37 | "dev-master": "1.1.x-dev"
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
19 |
20 | ./tests
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | src
32 |
33 | vendor
34 | tests
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/AccessToken.php:
--------------------------------------------------------------------------------
1 | token = $token;
27 |
28 | if ($expiresIn !== null) {
29 | if ($expiresIn instanceof \DateTime) {
30 | $this->expiresAt = $expiresIn;
31 | } else {
32 | $this->expiresAt = new \DateTime(sprintf('+%dseconds', $expiresIn));
33 | }
34 | }
35 | }
36 |
37 | /**
38 | * @return string
39 | */
40 | public function __toString()
41 | {
42 | return $this->token ?: '';
43 | }
44 |
45 | /**
46 | * Does a token string exist?
47 | *
48 | * @return bool
49 | */
50 | public function hasToken()
51 | {
52 | return !empty($this->token);
53 | }
54 |
55 | /**
56 | * @param \DateTime $expiresAt
57 | *
58 | * @return $this
59 | */
60 | public function setExpiresAt(\DateTime $expiresAt = null)
61 | {
62 | $this->expiresAt = $expiresAt;
63 |
64 | return $this;
65 | }
66 |
67 | /**
68 | * @return \DateTime
69 | */
70 | public function getExpiresAt()
71 | {
72 | return $this->expiresAt;
73 | }
74 |
75 | /**
76 | * @param null|string $token
77 | *
78 | * @return $this
79 | */
80 | public function setToken($token)
81 | {
82 | $this->token = $token;
83 |
84 | return $this;
85 | }
86 |
87 | /**
88 | * @return null|string
89 | */
90 | public function getToken()
91 | {
92 | return $this->token;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Authenticator.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class Authenticator implements AuthenticatorInterface
19 | {
20 | /**
21 | * The application ID.
22 | *
23 | * @var string
24 | */
25 | protected $appId;
26 |
27 | /**
28 | * The application secret.
29 | *
30 | * @var string
31 | */
32 | protected $appSecret;
33 |
34 | /**
35 | * A storage to use to store data between requests.
36 | *
37 | * @var DataStorageInterface storage
38 | */
39 | private $storage;
40 |
41 | /**
42 | * @var RequestManagerInterface
43 | */
44 | private $requestManager;
45 |
46 | /**
47 | * @param RequestManagerInterface $requestManager
48 | * @param string $appId
49 | * @param string $appSecret
50 | */
51 | public function __construct(RequestManagerInterface $requestManager, $appId, $appSecret)
52 | {
53 | $this->appId = $appId;
54 | $this->appSecret = $appSecret;
55 | $this->requestManager = $requestManager;
56 | }
57 |
58 | /**
59 | * {@inheritdoc}
60 | */
61 | public function fetchNewAccessToken(LinkedInUrlGeneratorInterface $urlGenerator)
62 | {
63 | $storage = $this->getStorage();
64 | $code = $this->getCode();
65 |
66 | if ($code === null) {
67 | /*
68 | * As a fallback, just return whatever is in the persistent
69 | * store, knowing nothing explicit (signed request, authorization
70 | * code, etc.) was present to shadow it.
71 | */
72 | return $storage->get('access_token');
73 | }
74 |
75 | try {
76 | $accessToken = $this->getAccessTokenFromCode($urlGenerator, $code);
77 | } catch (LinkedInException $e) {
78 | // code was bogus, so everything based on it should be invalidated.
79 | $storage->clearAll();
80 | throw $e;
81 | }
82 |
83 | $storage->set('code', $code);
84 | $storage->set('access_token', $accessToken);
85 |
86 | return $accessToken;
87 | }
88 |
89 | /**
90 | * Retrieves an access token for the given authorization code
91 | * (previously generated from www.linkedin.com on behalf of
92 | * a specific user). The authorization code is sent to www.linkedin.com
93 | * and a legitimate access token is generated provided the access token
94 | * and the user for which it was generated all match, and the user is
95 | * either logged in to LinkedIn or has granted an offline access permission.
96 | *
97 | * @param LinkedInUrlGeneratorInterface $urlGenerator
98 | * @param string $code An authorization code.
99 | *
100 | * @return AccessToken An access token exchanged for the authorization code.
101 | *
102 | * @throws LinkedInException
103 | */
104 | protected function getAccessTokenFromCode(LinkedInUrlGeneratorInterface $urlGenerator, $code)
105 | {
106 | if (empty($code)) {
107 | throw new LinkedInException('Could not get access token: The code was empty.');
108 | }
109 |
110 | $redirectUri = $this->getStorage()->get('redirect_uri');
111 | try {
112 | $url = $urlGenerator->getUrl('www', 'oauth/v2/accessToken');
113 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded'];
114 | $body = http_build_query(
115 | [
116 | 'grant_type' => 'authorization_code',
117 | 'code' => $code,
118 | 'redirect_uri' => $redirectUri,
119 | 'client_id' => $this->appId,
120 | 'client_secret' => $this->appSecret,
121 | ]
122 | );
123 |
124 | $response = ResponseConverter::convertToArray($this->getRequestManager()->sendRequest('POST', $url, $headers, $body));
125 | } catch (LinkedInTransferException $e) {
126 | // most likely that user very recently revoked authorization.
127 | // In any event, we don't have an access token, so throw an exception.
128 | throw new LinkedInException('Could not get access token: The user may have revoked the authorization response from LinkedIn.com was empty.', $e->getCode(), $e);
129 | }
130 |
131 | if (empty($response)) {
132 | throw new LinkedInException('Could not get access token: The response from LinkedIn.com was empty.');
133 | }
134 |
135 | $tokenData = array_merge(['access_token' => null, 'expires_in' => null], $response);
136 | $token = new AccessToken($tokenData['access_token'], $tokenData['expires_in']);
137 |
138 | if (!$token->hasToken()) {
139 | throw new LinkedInException('Could not get access token: The response from LinkedIn.com did not contain a token.');
140 | }
141 |
142 | return $token;
143 | }
144 |
145 | /**
146 | * {@inheritdoc}
147 | */
148 | public function getLoginUrl(LinkedInUrlGeneratorInterface $urlGenerator, $options = [])
149 | {
150 | // Generate a state
151 | $this->establishCSRFTokenState();
152 |
153 | // Build request params
154 | $requestParams = array_merge([
155 | 'response_type' => 'code',
156 | 'client_id' => $this->appId,
157 | 'state' => $this->getStorage()->get('state'),
158 | 'redirect_uri' => null,
159 | ], $options);
160 |
161 | // Save the redirect url for later
162 | $this->getStorage()->set('redirect_uri', $requestParams['redirect_uri']);
163 |
164 | // if 'scope' is passed as an array, convert to space separated list
165 | $scopeParams = isset($options['scope']) ? $options['scope'] : null;
166 | if ($scopeParams) {
167 | //if scope is an array
168 | if (is_array($scopeParams)) {
169 | $requestParams['scope'] = implode(' ', $scopeParams);
170 | } elseif (is_string($scopeParams)) {
171 | //if scope is a string with ',' => make it to an array
172 | $requestParams['scope'] = str_replace(',', ' ', $scopeParams);
173 | }
174 | }
175 |
176 | return $urlGenerator->getUrl('www', 'oauth/v2/authorization', $requestParams);
177 | }
178 |
179 | /**
180 | * Get the authorization code from the query parameters, if it exists,
181 | * and otherwise return null to signal no authorization code was
182 | * discovered.
183 | *
184 | * @return string|null The authorization code, or null if the authorization code not exists.
185 | *
186 | * @throws LinkedInException on invalid CSRF tokens
187 | */
188 | protected function getCode()
189 | {
190 | $storage = $this->getStorage();
191 |
192 | if (!GlobalVariableGetter::has('code')) {
193 | return;
194 | }
195 |
196 | if ($storage->get('code') === GlobalVariableGetter::get('code')) {
197 | //we have already validated this code
198 | return;
199 | }
200 |
201 | // if stored state does not exists
202 | if (null === $state = $storage->get('state')) {
203 | throw new LinkedInException('Could not find a stored CSRF state token.');
204 | }
205 |
206 | // if state not exists in the request
207 | if (!GlobalVariableGetter::has('state')) {
208 | throw new LinkedInException('Could not find a CSRF state token in the request.');
209 | }
210 |
211 | // if state exists in session and in request and if they are not equal
212 | if ($state !== GlobalVariableGetter::get('state')) {
213 | throw new LinkedInException('The CSRF state token from the request does not match the stored token.');
214 | }
215 |
216 | // CSRF state has done its job, so clear it
217 | $storage->clear('state');
218 |
219 | return GlobalVariableGetter::get('code');
220 | }
221 |
222 | /**
223 | * Lays down a CSRF state token for this process.
224 | */
225 | protected function establishCSRFTokenState()
226 | {
227 | $storage = $this->getStorage();
228 | if ($storage->get('state') === null) {
229 | $storage->set('state', md5(uniqid(mt_rand(), true)));
230 | }
231 | }
232 |
233 | /**
234 | * {@inheritdoc}
235 | */
236 | public function clearStorage()
237 | {
238 | $this->getStorage()->clearAll();
239 |
240 | return $this;
241 | }
242 |
243 | /**
244 | * @return DataStorageInterface
245 | */
246 | protected function getStorage()
247 | {
248 | if ($this->storage === null) {
249 | $this->storage = new SessionStorage();
250 | }
251 |
252 | return $this->storage;
253 | }
254 |
255 | /**
256 | * {@inheritdoc}
257 | */
258 | public function setStorage(DataStorageInterface $storage)
259 | {
260 | $this->storage = $storage;
261 |
262 | return $this;
263 | }
264 |
265 | /**
266 | * @return RequestManager
267 | */
268 | protected function getRequestManager()
269 | {
270 | return $this->requestManager;
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/src/AuthenticatorInterface.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | interface AuthenticatorInterface
15 | {
16 | /**
17 | * Tries to get a new access token from data storage or code. If it fails, it will return null.
18 | *
19 | * @param LinkedInUrlGeneratorInterface $urlGenerator
20 | *
21 | * @return AccessToken|null A valid user access token, or null if one could not be fetched.
22 | *
23 | * @throws LinkedInException
24 | */
25 | public function fetchNewAccessToken(LinkedInUrlGeneratorInterface $urlGenerator);
26 |
27 | /**
28 | * Generate a login url.
29 | *
30 | * @param LinkedInUrlGeneratorInterface $urlGenerator
31 | * @param array $options
32 | *
33 | * @return string
34 | */
35 | public function getLoginUrl(LinkedInUrlGeneratorInterface $urlGenerator, $options = []);
36 |
37 | /**
38 | * Clear the storage.
39 | *
40 | * @return $this
41 | */
42 | public function clearStorage();
43 |
44 | /**
45 | * @param DataStorageInterface $storage
46 | *
47 | * @return $this
48 | */
49 | public function setStorage(DataStorageInterface $storage);
50 | }
51 |
--------------------------------------------------------------------------------
/src/Exception/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class LinkedInException extends \Exception
11 | {
12 | }
13 |
--------------------------------------------------------------------------------
/src/Exception/LinkedInTransferException.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class LinkedInTransferException extends LinkedInException
11 | {
12 | }
13 |
--------------------------------------------------------------------------------
/src/Exception/LoginError.php:
--------------------------------------------------------------------------------
1 | name = $name;
29 | $this->description = $description;
30 | }
31 |
32 | /**
33 | * @return string
34 | */
35 | public function getName()
36 | {
37 | return $this->name;
38 | }
39 |
40 | /**
41 | * @return string
42 | */
43 | public function getDescription()
44 | {
45 | return $this->description;
46 | }
47 |
48 | /**
49 | * @return string
50 | */
51 | public function __toString()
52 | {
53 | return sprintf('Name: %s, Description: %s', $this->getName(), $this->getDescription());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Http/CurrentUrlGeneratorInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface CurrentUrlGeneratorInterface
11 | {
12 | /**
13 | * Returns the current URL.
14 | *
15 | * @return string The current URL
16 | */
17 | public function getCurrentUrl();
18 |
19 | /**
20 | * Should we trust forwarded headers?
21 | *
22 | * @param bool $trustForwarded
23 | *
24 | * @return $this
25 | */
26 | public function setTrustForwarded($trustForwarded);
27 | }
28 |
--------------------------------------------------------------------------------
/src/Http/GlobalVariableGetter.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class GlobalVariableGetter
11 | {
12 | /**
13 | * Returns true iff the $_REQUEST or $_GET variables has a key with $name.
14 | *
15 | * @param string $name
16 | *
17 | * @return bool
18 | */
19 | public static function has($name)
20 | {
21 | if (isset($_REQUEST[$name])) {
22 | return true;
23 | }
24 |
25 | return isset($_GET[$name]);
26 | }
27 |
28 | /**
29 | * Returns the value in $_REQUEST[$name] or $_GET[$name] if the former was empty. If no value found, return null.
30 | *
31 | * @param string $name
32 | *
33 | * @return mixed|null
34 | */
35 | public static function get($name)
36 | {
37 | if (isset($_REQUEST[$name])) {
38 | return $_REQUEST[$name];
39 | }
40 |
41 | if (isset($_GET[$name])) {
42 | return $_GET[$name];
43 | }
44 |
45 | return;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Http/LinkedInUrlGeneratorInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface LinkedInUrlGeneratorInterface
11 | {
12 | /**
13 | * Build the URL for given domain alias, path and parameters.
14 | *
15 | * @param $name string The name of the domain, 'www' or 'api'
16 | * @param $path string without a leading slash
17 | * @param $params array query parameters
18 | *
19 | * @return string The URL for the given parameters. The URL query MUST be build with PHP_QUERY_RFC3986
20 | */
21 | public function getUrl($name, $path = '', $params = []);
22 | }
23 |
--------------------------------------------------------------------------------
/src/Http/RequestManager.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class RequestManager implements RequestManagerInterface
18 | {
19 | /**
20 | * @var \Http\Client\HttpClient
21 | */
22 | private $httpClient;
23 |
24 | /**
25 | * @var \Http\Message\MessageFactory
26 | */
27 | private $messageFactory;
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function sendRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1')
33 | {
34 | $request = $this->getMessageFactory()->createRequest($method, $uri, $headers, $body, $protocolVersion);
35 |
36 | try {
37 | return $this->getHttpClient()->sendRequest($request);
38 | } catch (TransferException $e) {
39 | throw new LinkedInTransferException('Error while requesting data from LinkedIn.com: '.$e->getMessage(), $e->getCode(), $e);
40 | }
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function setHttpClient(HttpClient $httpClient)
47 | {
48 | $this->httpClient = $httpClient;
49 |
50 | return $this;
51 | }
52 |
53 | /**
54 | * @return HttpClient
55 | */
56 | protected function getHttpClient()
57 | {
58 | if ($this->httpClient === null) {
59 | $this->httpClient = HttpClientDiscovery::find();
60 | }
61 |
62 | return $this->httpClient;
63 | }
64 |
65 | /**
66 | * @param MessageFactory $messageFactory
67 | *
68 | * @return RequestManager
69 | */
70 | public function setMessageFactory(MessageFactory $messageFactory)
71 | {
72 | $this->messageFactory = $messageFactory;
73 |
74 | return $this;
75 | }
76 |
77 | /**
78 | * @return \Http\Message\MessageFactory
79 | */
80 | private function getMessageFactory()
81 | {
82 | if ($this->messageFactory === null) {
83 | $this->messageFactory = MessageFactoryDiscovery::find();
84 | }
85 |
86 | return $this->messageFactory;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Http/RequestManagerInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | interface RequestManagerInterface
14 | {
15 | /**
16 | * Send a request.
17 | *
18 | * @param string $method
19 | * @param string $uri
20 | * @param array $headers
21 | * @param string $body
22 | * @param string $protocolVersion
23 | *
24 | * @return \Psr\Http\Message\ResponseInterface
25 | *
26 | * @throws LinkedInTransferException
27 | */
28 | public function sendRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1');
29 |
30 | /**
31 | * @param \Http\Client\HttpClient $httpClient
32 | *
33 | * @return RequestManager
34 | */
35 | public function setHttpClient(HttpClient $httpClient);
36 | }
37 |
--------------------------------------------------------------------------------
/src/Http/ResponseConverter.php:
--------------------------------------------------------------------------------
1 | getBody()->__toString();
35 | case 'simple_xml':
36 | return self::convertToSimpleXml($response);
37 | case 'stream':
38 | return $response->getBody();
39 | case 'psr7':
40 | return $response;
41 | default:
42 | throw new InvalidArgumentException('Format "%s" is not supported', $dataType);
43 | }
44 | }
45 |
46 | /**
47 | * @param ResponseInterface $response
48 | *
49 | * @return string
50 | */
51 | public static function convertToArray(ResponseInterface $response)
52 | {
53 | return json_decode($response->getBody(), true);
54 | }
55 |
56 | /**
57 | * @param ResponseInterface $response
58 | *
59 | * @return \SimpleXMLElement
60 | *
61 | * @throws LinkedInTransferException
62 | */
63 | public static function convertToSimpleXml(ResponseInterface $response)
64 | {
65 | $body = $response->getBody();
66 | try {
67 | return new \SimpleXMLElement((string) $body ?: '');
68 | } catch (\Exception $e) {
69 | throw new LinkedInTransferException('Unable to parse response body into XML.');
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Http/UrlGenerator.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | class UrlGenerator implements UrlGeneratorInterface
9 | {
10 | /**
11 | * @var array knownLinkedInParams
12 | *
13 | * A list of params that might be in the query string
14 | */
15 | public static $knownLinkedInParams = ['state', 'code', 'access_token', 'user'];
16 |
17 | /**
18 | * @var array domainMap
19 | *
20 | * Maps aliases to LinkedIn domains.
21 | */
22 | public static $domainMap = [
23 | 'api' => 'https://api.linkedin.com/',
24 | 'www' => 'https://www.linkedin.com/',
25 | ];
26 |
27 | /**
28 | * @var bool
29 | *
30 | * Indicates if we trust HTTP_X_FORWARDED_* headers.
31 | */
32 | protected $trustForwarded = false;
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | public function getUrl($name, $path = '', $params = [])
38 | {
39 | $url = self::$domainMap[$name];
40 | if ($path) {
41 | if ($path[0] === '/') {
42 | $path = substr($path, 1);
43 | }
44 | $url .= $path;
45 | }
46 |
47 | if (!empty($params)) {
48 | // does it exist a query string?
49 | $queryString = parse_url($url, PHP_URL_QUERY);
50 | if (empty($queryString)) {
51 | $url .= '?';
52 | } else {
53 | $url .= '&';
54 | }
55 |
56 | // it needs to be PHP_QUERY_RFC3986. We want to have %20 between scopes
57 | $url .= http_build_query($params, null, '&', PHP_QUERY_RFC3986);
58 | }
59 |
60 | return $url;
61 | }
62 |
63 | /**
64 | * {@inheritdoc}
65 | */
66 | public function getCurrentUrl()
67 | {
68 | $protocol = $this->getHttpProtocol().'://';
69 | $host = $this->getHttpHost();
70 | $currentUrl = $protocol.$host.$_SERVER['REQUEST_URI'];
71 | $parts = parse_url($currentUrl);
72 |
73 | $query = '';
74 | if (!empty($parts['query'])) {
75 | // drop known linkedin params
76 | $query = $this->dropLinkedInParams($parts['query']);
77 | }
78 |
79 | // use port if non default
80 | $port =
81 | isset($parts['port']) &&
82 | (($protocol === 'http://' && $parts['port'] !== 80) ||
83 | ($protocol === 'https://' && $parts['port'] !== 443))
84 | ? ':'.$parts['port'] : '';
85 |
86 | // rebuild
87 | return $protocol.$parts['host'].$port.$parts['path'].$query;
88 | }
89 |
90 | /**
91 | * Drop known LinkedIn params. Ie those in self::$knownLinkeInParams.
92 | *
93 | * @param string $query
94 | *
95 | * @return string query without LinkedIn params. This string is prepended with a question mark '?'
96 | */
97 | protected function dropLinkedInParams($query)
98 | {
99 | if ($query == '') {
100 | return '';
101 | }
102 |
103 | $params = explode('&', $query);
104 | foreach ($params as $i => $param) {
105 | /*
106 | * A key or key/value pair might me 'foo=bar', 'foo=', or 'foo'.
107 | */
108 | //get the first value of the array you will get when you explode()
109 | list($key) = explode('=', $param, 2);
110 | if (in_array($key, self::$knownLinkedInParams)) {
111 | unset($params[$i]);
112 | }
113 | }
114 |
115 | //assert: params is an array. It might be empty
116 | if (!empty($params)) {
117 | return '?'.implode($params, '&');
118 | }
119 |
120 | return '';
121 | }
122 |
123 | /**
124 | * Get the host.
125 | *
126 | *
127 | * @return mixed
128 | */
129 | protected function getHttpHost()
130 | {
131 | if ($this->trustForwarded && isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
132 | return $_SERVER['HTTP_X_FORWARDED_HOST'];
133 | }
134 |
135 | return $_SERVER['HTTP_HOST'];
136 | }
137 |
138 | /**
139 | * Get the protocol.
140 | *
141 | *
142 | * @return string
143 | */
144 | protected function getHttpProtocol()
145 | {
146 | if ($this->trustForwarded && isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
147 | if ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
148 | return 'https';
149 | }
150 |
151 | return 'http';
152 | }
153 |
154 | /*apache + variants specific way of checking for https*/
155 | if (isset($_SERVER['HTTPS']) &&
156 | ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] == 1)) {
157 | return 'https';
158 | }
159 |
160 | /*nginx way of checking for https*/
161 | if (isset($_SERVER['SERVER_PORT']) &&
162 | ($_SERVER['SERVER_PORT'] === '443')) {
163 | return 'https';
164 | }
165 |
166 | return 'http';
167 | }
168 |
169 | /**
170 | * {@inheritdoc}
171 | */
172 | public function setTrustForwarded($trustForwarded)
173 | {
174 | $this->trustForwarded = $trustForwarded;
175 |
176 | return $this;
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/Http/UrlGeneratorInterface.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | interface UrlGeneratorInterface extends LinkedInUrlGeneratorInterface, CurrentUrlGeneratorInterface
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/src/LinkedIn.php:
--------------------------------------------------------------------------------
1 |
32 | */
33 | class LinkedIn implements LinkedInInterface
34 | {
35 | /**
36 | * The OAuth access token received in exchange for a valid authorization
37 | * code. null means the access token has yet to be determined.
38 | *
39 | * @var AccessToken
40 | */
41 | protected $accessToken = null;
42 |
43 | /**
44 | * @var string format
45 | */
46 | private $format;
47 |
48 | /**
49 | * @var string responseFormat
50 | */
51 | private $responseDataType;
52 |
53 | /**
54 | * @var ResponseInterface
55 | */
56 | private $lastResponse;
57 |
58 | /**
59 | * @var RequestManager
60 | */
61 | private $requestManager;
62 |
63 | /**
64 | * @var Authenticator
65 | */
66 | private $authenticator;
67 |
68 | /**
69 | * @var UrlGeneratorInterface
70 | */
71 | private $urlGenerator;
72 |
73 | /**
74 | * Constructor.
75 | *
76 | * @param string $appId
77 | * @param string $appSecret
78 | * @param string $format 'json', 'xml'
79 | * @param string $responseDataType 'array', 'string', 'simple_xml' 'psr7', 'stream'
80 | */
81 | public function __construct($appId, $appSecret, $format = 'json', $responseDataType = 'array')
82 | {
83 | $this->format = $format;
84 | $this->responseDataType = $responseDataType;
85 |
86 | $this->requestManager = new RequestManager();
87 | $this->authenticator = new Authenticator($this->requestManager, $appId, $appSecret);
88 | }
89 |
90 | /**
91 | * {@inheritdoc}
92 | */
93 | public function isAuthenticated()
94 | {
95 | $accessToken = $this->getAccessToken();
96 | if ($accessToken === null) {
97 | return false;
98 | }
99 |
100 | $user = $this->api('GET', '/v1/people/~:(id,firstName,lastName)', ['format' => 'json', 'response_data_type' => 'array']);
101 |
102 | return !empty($user['id']);
103 | }
104 |
105 | /**
106 | * {@inheritdoc}
107 | */
108 | public function api($method, $resource, array $options = [])
109 | {
110 | // Add access token to the headers
111 | $options['headers']['Authorization'] = sprintf('Bearer %s', (string) $this->getAccessToken());
112 |
113 | // Do logic and adjustments to the options
114 | $requestFormat = $this->filterRequestOption($options);
115 |
116 | // Generate an url
117 | $url = $this->getUrlGenerator()->getUrl(
118 | 'api',
119 | $resource,
120 | isset($options['query']) ? $options['query'] : []
121 | );
122 |
123 | $body = isset($options['body']) ? $options['body'] : null;
124 | $this->lastResponse = $this->getRequestManager()->sendRequest($method, $url, $options['headers'], $body);
125 |
126 | //Get the response data format
127 | if (isset($options['response_data_type'])) {
128 | $responseDataType = $options['response_data_type'];
129 | } else {
130 | $responseDataType = $this->getResponseDataType();
131 | }
132 |
133 | return ResponseConverter::convert($this->lastResponse, $requestFormat, $responseDataType);
134 | }
135 |
136 | /**
137 | * Modify and filter the request options. Make sure we use the correct query parameters and headers.
138 | *
139 | * @param array &$options
140 | *
141 | * @return string the request format to use
142 | */
143 | protected function filterRequestOption(array &$options)
144 | {
145 | if (isset($options['json'])) {
146 | $options['format'] = 'json';
147 | $options['body'] = json_encode($options['json']);
148 | } elseif (!isset($options['format'])) {
149 | // Make sure we always have a format
150 | $options['format'] = $this->getFormat();
151 | }
152 |
153 | // Set correct headers for this format
154 | switch ($options['format']) {
155 | case 'xml':
156 | $options['headers']['Content-Type'] = 'text/xml';
157 | break;
158 | case 'json':
159 | $options['headers']['Content-Type'] = 'application/json';
160 | $options['headers']['x-li-format'] = 'json';
161 | $options['query']['format'] = 'json';
162 | break;
163 | default:
164 | // Do nothing
165 | }
166 |
167 | return $options['format'];
168 | }
169 |
170 | /**
171 | * {@inheritdoc}
172 | */
173 | public function getLoginUrl($options = [])
174 | {
175 | $urlGenerator = $this->getUrlGenerator();
176 |
177 | // Set redirect_uri to current URL if not defined
178 | if (!isset($options['redirect_uri'])) {
179 | $options['redirect_uri'] = $urlGenerator->getCurrentUrl();
180 | }
181 |
182 | return $this->getAuthenticator()->getLoginUrl($urlGenerator, $options);
183 | }
184 |
185 | /**
186 | * See docs for LinkedIn::api().
187 | *
188 | * @param string $resource
189 | * @param array $options
190 | *
191 | * @return mixed
192 | */
193 | public function get($resource, array $options = [])
194 | {
195 | return $this->api('GET', $resource, $options);
196 | }
197 |
198 | /**
199 | * See docs for LinkedIn::api().
200 | *
201 | * @param string $resource
202 | * @param array $options
203 | *
204 | * @return mixed
205 | */
206 | public function post($resource, array $options = [])
207 | {
208 | return $this->api('POST', $resource, $options);
209 | }
210 |
211 | /**
212 | * {@inheritdoc}
213 | */
214 | public function clearStorage()
215 | {
216 | $this->getAuthenticator()->clearStorage();
217 |
218 | return $this;
219 | }
220 |
221 | /**
222 | * {@inheritdoc}
223 | */
224 | public function hasError()
225 | {
226 | return GlobalVariableGetter::has('error');
227 | }
228 |
229 | /**
230 | * {@inheritdoc}
231 | */
232 | public function getError()
233 | {
234 | if ($this->hasError()) {
235 | return new LoginError(GlobalVariableGetter::get('error'), GlobalVariableGetter::get('error_description'));
236 | }
237 | }
238 |
239 | /**
240 | * Get the default format to use when sending requests.
241 | *
242 | * @return string
243 | */
244 | protected function getFormat()
245 | {
246 | return $this->format;
247 | }
248 |
249 | /**
250 | * {@inheritdoc}
251 | */
252 | public function setFormat($format)
253 | {
254 | $this->format = $format;
255 |
256 | return $this;
257 | }
258 |
259 | /**
260 | * Get the default data type to be returned as a response.
261 | *
262 | * @return string
263 | */
264 | protected function getResponseDataType()
265 | {
266 | return $this->responseDataType;
267 | }
268 |
269 | /**
270 | * {@inheritdoc}
271 | */
272 | public function setResponseDataType($responseDataType)
273 | {
274 | $this->responseDataType = $responseDataType;
275 |
276 | return $this;
277 | }
278 |
279 | /**
280 | * {@inheritdoc}
281 | */
282 | public function getLastResponse()
283 | {
284 | return $this->lastResponse;
285 | }
286 |
287 | /**
288 | * {@inheritdoc}
289 | */
290 | public function getAccessToken()
291 | {
292 | if ($this->accessToken === null) {
293 | if (null !== $newAccessToken = $this->getAuthenticator()->fetchNewAccessToken($this->getUrlGenerator())) {
294 | $this->setAccessToken($newAccessToken);
295 | }
296 | }
297 |
298 | // return the new access token or null if none found
299 | return $this->accessToken;
300 | }
301 |
302 | /**
303 | * {@inheritdoc}
304 | */
305 | public function setAccessToken($accessToken)
306 | {
307 | if (!($accessToken instanceof AccessToken)) {
308 | $accessToken = new AccessToken($accessToken);
309 | }
310 |
311 | $this->accessToken = $accessToken;
312 |
313 | return $this;
314 | }
315 |
316 | /**
317 | * {@inheritdoc}
318 | */
319 | public function setUrlGenerator(UrlGeneratorInterface $urlGenerator)
320 | {
321 | $this->urlGenerator = $urlGenerator;
322 |
323 | return $this;
324 | }
325 |
326 | /**
327 | * @return UrlGeneratorInterface
328 | */
329 | protected function getUrlGenerator()
330 | {
331 | if ($this->urlGenerator === null) {
332 | $this->urlGenerator = new UrlGenerator();
333 | }
334 |
335 | return $this->urlGenerator;
336 | }
337 |
338 | /**
339 | * {@inheritdoc}
340 | */
341 | public function setStorage(DataStorageInterface $storage)
342 | {
343 | $this->getAuthenticator()->setStorage($storage);
344 |
345 | return $this;
346 | }
347 |
348 | /**
349 | * {@inheritdoc}
350 | */
351 | public function setHttpClient(HttpClient $client)
352 | {
353 | $this->getRequestManager()->setHttpClient($client);
354 |
355 | return $this;
356 | }
357 |
358 | /**
359 | * {@inheritdoc}
360 | */
361 | public function setHttpMessageFactory(MessageFactory $factory)
362 | {
363 | $this->getRequestManager()->setMessageFactory($factory);
364 |
365 | return $this;
366 | }
367 |
368 | /**
369 | * @return RequestManager
370 | */
371 | protected function getRequestManager()
372 | {
373 | return $this->requestManager;
374 | }
375 |
376 | /**
377 | * @return Authenticator
378 | */
379 | protected function getAuthenticator()
380 | {
381 | return $this->authenticator;
382 | }
383 | }
384 |
--------------------------------------------------------------------------------
/src/LinkedInInterface.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | interface LinkedInInterface
18 | {
19 | /**
20 | * Is the current user authenticated?
21 | *
22 | * @return bool
23 | */
24 | public function isAuthenticated();
25 |
26 | /**
27 | * Make an API call. Read about what calls that are possible here: https://developer.linkedin.com/docs/rest-api.
28 | *
29 | * Example:
30 | * $linkedIn->api('GET', '/v1/people/~:(id,firstName,lastName,headline)');
31 | *
32 | * The options:
33 | * - body: the body of the request
34 | * - format: the format you are using to send the request
35 | * - headers: array with headers to use
36 | * - response_data_type: the data type to get back
37 | * - query: query parameters to the request
38 | *
39 | * @param string $method This is the HTTP verb
40 | * @param string $resource everything after the domain in the URL.
41 | * @param array $options See the readme for option description.
42 | *
43 | * @return mixed this depends on the response_data_type parameter.
44 | */
45 | public function api($method, $resource, array $options = []);
46 |
47 | /**
48 | * Get a login URL where the user can put his/hers LinkedIn credentials and authorize the application.
49 | *
50 | * The options:
51 | * - redirect_uri: the url to go to after a successful login
52 | * - scope: comma (or space) separated list of requested extended permissions
53 | *
54 | * @param array $options Provide custom parameters
55 | *
56 | * @return string The URL for the login flow
57 | */
58 | public function getLoginUrl($options = []);
59 |
60 | /**
61 | * See docs for LinkedIn::api().
62 | *
63 | * @param string $resource
64 | * @param array $options
65 | *
66 | * @return mixed
67 | */
68 | public function get($resource, array $options = []);
69 |
70 | /**
71 | * See docs for LinkedIn::api().
72 | *
73 | * @param string $resource
74 | * @param array $options
75 | *
76 | * @return mixed
77 | */
78 | public function post($resource, array $options = []);
79 |
80 | /**
81 | * Clear the data storage. This will forget everything about the user and authentication process.
82 | *
83 | * @return $this
84 | */
85 | public function clearStorage();
86 |
87 | /**
88 | * If the user has canceled the login we will return with an error.
89 | *
90 | * @return bool
91 | */
92 | public function hasError();
93 |
94 | /**
95 | * Returns a LoginError or null.
96 | *
97 | * @return LoginError|null
98 | */
99 | public function getError();
100 |
101 | /**
102 | * Set the default format to use when sending requests.
103 | *
104 | * @param string $format
105 | *
106 | * @return $this
107 | */
108 | public function setFormat($format);
109 |
110 | /**
111 | * Set the default data type to be returned as a response.
112 | *
113 | * @param string $responseDataType
114 | *
115 | * @return $this
116 | */
117 | public function setResponseDataType($responseDataType);
118 |
119 | /**
120 | * Get the last response. This will always return a PSR-7 response no matter of the data type used.
121 | *
122 | * @return ResponseInterface|null
123 | */
124 | public function getLastResponse();
125 |
126 | /**
127 | * Returns an access token. If we do not have one in memory, try to fetch one from a *code* in the $_REQUEST.
128 | *
129 | * @return AccessToken|null The access token of null if the access token is not found
130 | */
131 | public function getAccessToken();
132 |
133 | /**
134 | * If you have stored a previous access token in a storage (database) you could set it here. After setting an
135 | * access token you have to make sure to verify it is still valid by running LinkedIn::isAuthenticated.
136 | *
137 | * @param string|AccessToken $accessToken
138 | *
139 | * @return $this
140 | */
141 | public function setAccessToken($accessToken);
142 |
143 | /**
144 | * Set a URL generator.
145 | *
146 | * @param UrlGeneratorInterface $urlGenerator
147 | *
148 | * @return $this
149 | */
150 | public function setUrlGenerator(UrlGeneratorInterface $urlGenerator);
151 |
152 | /**
153 | * Set a data storage.
154 | *
155 | * @param DataStorageInterface $storage
156 | *
157 | * @return $this
158 | */
159 | public function setStorage(DataStorageInterface $storage);
160 |
161 | /**
162 | * Set a http client.
163 | *
164 | * @param HttpClient $client
165 | *
166 | * @return $this
167 | */
168 | public function setHttpClient(HttpClient $client);
169 |
170 | /**
171 | * Set a http message factory.
172 | *
173 | * @param MessageFactory $factory
174 | *
175 | * @return $this
176 | */
177 | public function setHttpMessageFactory(MessageFactory $factory);
178 | }
179 |
--------------------------------------------------------------------------------
/src/Storage/BaseDataStorage.php:
--------------------------------------------------------------------------------
1 | clear($key);
21 | }
22 | }
23 |
24 | /**
25 | * Validate key. Throws an exception if key is not valid.
26 | *
27 | * @param string $key
28 | *
29 | * @throws InvalidArgumentException
30 | */
31 | protected function validateKey($key)
32 | {
33 | if (!in_array($key, self::$validKeys)) {
34 | throw new InvalidArgumentException('Unsupported key "%s" passed to LinkedIn data storage. Valid keys are: %s', $key, implode(', ', self::$validKeys));
35 | }
36 | }
37 |
38 | /**
39 | * Generate an ID to use with the data storage.
40 | *
41 | * @param $key
42 | *
43 | * @return string
44 | */
45 | protected function getStorageKeyId($key)
46 | {
47 | return 'linkedIn_'.$key;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Storage/DataStorageInterface.php:
--------------------------------------------------------------------------------
1 | validateKey($key);
20 | $name = $this->getStorageKeyId($key);
21 |
22 | return Session::put($name, $value);
23 | }
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function get($key)
29 | {
30 | $this->validateKey($key);
31 | $name = $this->getStorageKeyId($key);
32 |
33 | return Session::get($name);
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | public function clear($key)
40 | {
41 | $this->validateKey($key);
42 | $name = $this->getStorageKeyId($key);
43 |
44 | return Session::forget($name);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Storage/SessionStorage.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class SessionStorage extends BaseDataStorage
11 | {
12 | public function __construct()
13 | {
14 | //start the session if it not already been started
15 | if (php_sapi_name() !== 'cli') {
16 | if (session_id() === '') {
17 | session_start();
18 | }
19 | }
20 | }
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function set($key, $value)
26 | {
27 | $this->validateKey($key);
28 |
29 | $name = $this->getStorageKeyId($key);
30 | $_SESSION[$name] = $value;
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function get($key)
37 | {
38 | $this->validateKey($key);
39 | $name = $this->getStorageKeyId($key);
40 |
41 | return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | public function clear($key)
48 | {
49 | $this->validateKey($key);
50 |
51 | $name = $this->getStorageKeyId($key);
52 | if (isset($_SESSION[$name])) {
53 | unset($_SESSION[$name]);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/AccessTokenTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('', $token);
14 |
15 | $token->setToken('foobar');
16 | $this->assertEquals('foobar', $token);
17 | }
18 |
19 | public function testConstructor()
20 | {
21 | $token = new AccessToken('foobar', 10);
22 | $this->assertInstanceOf('\DateTime', $token->getExpiresAt());
23 | $this->assertEquals('foobar', $token->getToken());
24 |
25 | $token = new AccessToken();
26 | $this->assertNull($token->getExpiresAt());
27 | $this->assertEmpty($token->getToken());
28 |
29 | $token = new AccessToken(null, new \DateTime('+2minutes'));
30 | $this->assertInstanceOf('\DateTime', $token->getExpiresAt());
31 | }
32 |
33 | public function testSetExpiresAt()
34 | {
35 | $token = new AccessToken();
36 | $token->setExpiresAt(new \DateTime('+2minutes'));
37 | $this->assertInstanceOf('\DateTime', $token->getExpiresAt());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/AuthenticatorTest.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class AuthenticatorTest extends \PHPUnit_Framework_TestCase
13 | {
14 | const APP_ID = '123456789';
15 | const APP_SECRET = '987654321';
16 |
17 | private function getRequestManagerMock()
18 | {
19 | return m::mock('Happyr\LinkedIn\Http\RequestManager');
20 | }
21 |
22 | public function testGetLoginUrl()
23 | {
24 | $expected = 'loginUrl';
25 | $state = 'random';
26 | $params = [
27 | 'response_type' => 'code',
28 | 'client_id' => self::APP_ID,
29 | 'redirect_uri' => null,
30 | 'state' => $state,
31 | ];
32 |
33 | $storage = $this->getMock('Happyr\LinkedIn\Storage\DataStorageInterface');
34 | $storage->method('get')->with('state')->willReturn($state);
35 |
36 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['establishCSRFTokenState', 'getStorage'], [$this->getRequestManagerMock(), self::APP_ID, self::APP_SECRET]);
37 | $auth->expects($this->exactly(2))->method('establishCSRFTokenState')->willReturn(null);
38 | $auth->method('getStorage')->will($this->returnValue($storage));
39 |
40 | $generator = m::mock('Happyr\LinkedIn\Http\LinkedInUrlGeneratorInterface')
41 | ->shouldReceive('getUrl')->once()->with('www', 'oauth/v2/authorization', $params)->andReturn($expected)
42 | ->getMock();
43 |
44 | $this->assertEquals($expected, $auth->getLoginUrl($generator));
45 |
46 | /*
47 | * Test with a url in the param
48 | */
49 | $otherUrl = 'otherUrl';
50 | $scope = ['foo', 'bar', 'baz'];
51 | $params = [
52 | 'response_type' => 'code',
53 | 'client_id' => self::APP_ID,
54 | 'redirect_uri' => $otherUrl,
55 | 'state' => $state,
56 | 'scope' => 'foo bar baz',
57 | ];
58 |
59 | $generator = m::mock('Happyr\LinkedIn\Http\LinkedInUrlGeneratorInterface')
60 | ->shouldReceive('getUrl')->once()->with('www', 'oauth/v2/authorization', $params)->andReturn($expected)
61 | ->getMock();
62 |
63 | $this->assertEquals($expected, $auth->getLoginUrl($generator, ['redirect_uri' => $otherUrl, 'scope' => $scope]));
64 | }
65 |
66 | public function testFetchNewAccessToken()
67 | {
68 | $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator');
69 | $code = 'newCode';
70 | $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface')
71 | ->shouldReceive('set')->once()->with('code', $code)
72 | ->shouldReceive('set')->once()->with('access_token', 'at')
73 | ->getMock();
74 |
75 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getCode', 'getStorage', 'getAccessTokenFromCode'], [], '', false);
76 | $auth->expects($this->any())->method('getStorage')->will($this->returnValue($storage));
77 | $auth->expects($this->once())->method('getAccessTokenFromCode')->with($generator, $code)->will($this->returnValue('at'));
78 | $auth->expects($this->once())->method('getCode')->will($this->returnValue($code));
79 |
80 | $this->assertEquals('at', $auth->fetchNewAccessToken($generator));
81 | }
82 |
83 | /**
84 | * @expectedException \Happyr\LinkedIn\Exception\LinkedInException
85 | */
86 | public function testFetchNewAccessTokenFail()
87 | {
88 | $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator');
89 | $code = 'newCode';
90 | $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface')
91 | ->shouldReceive('clearAll')->once()
92 | ->getMock();
93 |
94 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getCode', 'getStorage', 'getAccessTokenFromCode'], [], '', false);
95 | $auth->expects($this->any())->method('getStorage')->will($this->returnValue($storage));
96 | $auth->expects($this->once())->method('getAccessTokenFromCode')->with($generator, $code)->willThrowException(new LinkedInException());
97 | $auth->expects($this->once())->method('getCode')->will($this->returnValue($code));
98 |
99 | $auth->fetchNewAccessToken($generator);
100 | }
101 |
102 | public function testFetchNewAccessTokenNoCode()
103 | {
104 | $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator');
105 | $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface')
106 | ->shouldReceive('get')->with('code')->andReturn('foobar')
107 | ->shouldReceive('get')->once()->with('access_token')->andReturn('baz')
108 | ->getMock();
109 |
110 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getCode', 'getStorage'], [], '', false);
111 | $auth->expects($this->any())->method('getStorage')->will($this->returnValue($storage));
112 | $auth->expects($this->once())->method('getCode');
113 |
114 | $this->assertEquals('baz', $auth->fetchNewAccessToken($generator));
115 | }
116 |
117 | /**
118 | * @expectedException \Happyr\LinkedIn\Exception\LinkedInException
119 | */
120 | public function testGetAccessTokenFromCodeEmptyString()
121 | {
122 | $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator');
123 |
124 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode');
125 | $method->setAccessible(true);
126 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', [], [], '', false);
127 |
128 | $method->invoke($auth, $generator, '');
129 | }
130 |
131 | /**
132 | * @expectedException \Happyr\LinkedIn\Exception\LinkedInException
133 | */
134 | public function testGetAccessTokenFromCodeNull()
135 | {
136 | $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator');
137 |
138 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode');
139 | $method->setAccessible(true);
140 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', [], [], '', false);
141 |
142 | $method->invoke($auth, $generator, null);
143 | }
144 |
145 | /**
146 | * @expectedException \Happyr\LinkedIn\Exception\LinkedInException
147 | */
148 | public function testGetAccessTokenFromCodeFalse()
149 | {
150 | $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator');
151 |
152 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode');
153 | $method->setAccessible(true);
154 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', [], [], '', false);
155 |
156 | $method->invoke($auth, $generator, false);
157 | }
158 |
159 | public function testGetAccessTokenFromCode()
160 | {
161 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode');
162 | $method->setAccessible(true);
163 |
164 | $code = 'code';
165 | $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator')
166 | ->shouldReceive('getUrl')->with(
167 | 'www',
168 | 'oauth/v2/accessToken'
169 | )->andReturn('url')
170 | ->getMock();
171 |
172 | $response = ['access_token' => 'foobar', 'expires_in' => 10];
173 | $auth = $this->prepareGetAccessTokenFromCode($code, $response);
174 | $token = $method->invoke($auth, $generator, $code);
175 | $this->assertEquals('foobar', $token, 'Standard get access token form code');
176 | }
177 |
178 | /**
179 | * @expectedException \Happyr\LinkedIn\Exception\LinkedInException
180 | */
181 | public function testGetAccessTokenFromCodeNoTokenInResponse()
182 | {
183 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode');
184 | $method->setAccessible(true);
185 |
186 | $code = 'code';
187 | $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator')
188 | ->shouldReceive('getUrl')->with(
189 | 'www',
190 | 'oauth/v2/accessToken'
191 | )->andReturn('url')
192 | ->getMock();
193 |
194 | $response = ['foo' => 'bar'];
195 | $auth = $this->prepareGetAccessTokenFromCode($code, $response);
196 | $this->assertNull($method->invoke($auth, $generator, $code), 'Found array but no access token');
197 | }
198 |
199 | /**
200 | * @expectedException \Happyr\LinkedIn\Exception\LinkedInException
201 | */
202 | public function testGetAccessTokenFromCodeEmptyResponse()
203 | {
204 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getAccessTokenFromCode');
205 | $method->setAccessible(true);
206 |
207 | $code = 'code';
208 | $generator = m::mock('Happyr\LinkedIn\Http\UrlGenerator')
209 | ->shouldReceive('getUrl')->with(
210 | 'www',
211 | 'oauth/v2/accessToken'
212 | )->andReturn('url')
213 | ->getMock();
214 |
215 | $response = '';
216 | $auth = $this->prepareGetAccessTokenFromCode($code, $response);
217 | $this->assertNull($method->invoke($auth, $generator, $code), 'Empty result');
218 | }
219 |
220 | /**
221 | * Default stuff for GetAccessTokenFromCode.
222 | *
223 | * @param $response
224 | *
225 | * @return array
226 | */
227 | protected function prepareGetAccessTokenFromCode($code, $responseData)
228 | {
229 | $response = new Response(200, [], json_encode($responseData));
230 | $currentUrl = 'foobar';
231 |
232 | $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface')
233 | ->shouldReceive('get')->with('redirect_uri')->andReturn($currentUrl)
234 | ->getMock();
235 |
236 | $requestManager = m::mock('Happyr\LinkedIn\Http\RequestManager')
237 | ->shouldReceive('sendRequest')->once()->with('POST', 'url', [
238 | 'Content-Type' => 'application/x-www-form-urlencoded',
239 | ], http_build_query([
240 | 'grant_type' => 'authorization_code',
241 | 'code' => $code,
242 | 'redirect_uri' => $currentUrl,
243 | 'client_id' => self::APP_ID,
244 | 'client_secret' => self::APP_SECRET,
245 | ]))->andReturn($response)
246 | ->getMock();
247 |
248 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getStorage'], [$requestManager, self::APP_ID, self::APP_SECRET]);
249 | $auth->expects($this->any())->method('getStorage')->will($this->returnValue($storage));
250 |
251 | return $auth;
252 | }
253 |
254 | public function testEstablishCSRFTokenState()
255 | {
256 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'establishCSRFTokenState');
257 | $method->setAccessible(true);
258 |
259 | $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface')
260 | ->shouldReceive('get')->with('state')->andReturn(null, 'state')
261 | ->shouldReceive('set')->once()->with('state', \Mockery::on(function (&$param) {
262 | return !empty($param);
263 | }))
264 | ->getMock();
265 |
266 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getStorage'], [], '', false);
267 | $auth->expects($this->any())->method('getStorage')->will($this->returnValue($storage));
268 |
269 | // Make sure we only set the state once
270 | $method->invoke($auth);
271 | $method->invoke($auth);
272 | }
273 |
274 | public function testGetCodeEmpty()
275 | {
276 | unset($_REQUEST['code']);
277 | unset($_GET['code']);
278 |
279 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getCode');
280 | $method->setAccessible(true);
281 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', [], [], '', false);
282 |
283 | $this->assertNull($method->invoke($auth));
284 | }
285 |
286 | public function testGetCode()
287 | {
288 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getCode');
289 | $method->setAccessible(true);
290 | $state = 'bazbar';
291 |
292 | $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface')
293 | ->shouldReceive('clear')->once()->with('state')
294 | ->shouldReceive('get')->once()->with('code')->andReturn(null)
295 | ->shouldReceive('get')->once()->with('state')->andReturn($state)
296 | ->getMock();
297 |
298 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getStorage'], [], '', false);
299 | $auth->expects($this->once())->method('getStorage')->will($this->returnValue($storage));
300 |
301 | $_REQUEST['code'] = 'foobar';
302 | $_REQUEST['state'] = $state;
303 |
304 | $this->assertEquals('foobar', $method->invoke($auth));
305 | }
306 |
307 | /**
308 | * @expectedException \Happyr\LinkedIn\Exception\LinkedInException
309 | */
310 | public function testGetCodeInvalidCode()
311 | {
312 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getCode');
313 | $method->setAccessible(true);
314 |
315 | $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface')
316 | ->shouldReceive('get')->once()->with('code')->andReturn(null)
317 | ->shouldReceive('get')->once()->with('state')->andReturn('bazbar')
318 | ->getMock();
319 |
320 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getStorage'], [], '', false);
321 | $auth->expects($this->once())->method('getStorage')->will($this->returnValue($storage));
322 |
323 | $_REQUEST['code'] = 'foobar';
324 | $_REQUEST['state'] = 'invalid';
325 |
326 | $this->assertEquals('foobar', $method->invoke($auth));
327 | }
328 |
329 | public function testGetCodeUsedCode()
330 | {
331 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getCode');
332 | $method->setAccessible(true);
333 |
334 | $storage = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface')
335 | ->shouldReceive('get')->once()->with('code')->andReturn('foobar')
336 | ->getMock();
337 |
338 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getStorage'], [], '', false);
339 | $auth->expects($this->once())->method('getStorage')->will($this->returnValue($storage));
340 |
341 | $_REQUEST['code'] = 'foobar';
342 |
343 | $this->assertEquals(null, $method->invoke($auth));
344 | }
345 |
346 | public function testStorageAccessors()
347 | {
348 | $method = new \ReflectionMethod('Happyr\LinkedIn\Authenticator', 'getStorage');
349 | $method->setAccessible(true);
350 | $requestManager = $this->getRequestManagerMock();
351 | $auth = new Authenticator($requestManager, self::APP_ID, self::APP_SECRET);
352 |
353 | // test default
354 | $this->assertInstanceOf('Happyr\LinkedIn\Storage\SessionStorage', $method->invoke($auth));
355 |
356 | $object = m::mock('Happyr\LinkedIn\Storage\DataStorageInterface');
357 | $auth->setStorage($object);
358 | $this->assertEquals($object, $method->invoke($auth));
359 | }
360 | }
361 |
--------------------------------------------------------------------------------
/tests/Exceptions/LoginErrorTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('foo', $error->getName());
17 | $this->assertEquals('bar', $error->getDescription());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Http/ResponseConverterTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf('Psr\Http\Message\ResponseInterface', $result);
16 |
17 | $result = ResponseConverter::convert($response, 'json', 'stream');
18 | $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $result);
19 |
20 | $result = ResponseConverter::convert($response, 'json', 'string');
21 | $this->assertTrue(is_string($result));
22 | $this->assertEquals($body, $result);
23 |
24 | $result = ResponseConverter::convert($response, 'json', 'array');
25 | $this->assertTrue(is_array($result));
26 |
27 | $body = '
28 |
29 | foo
30 | bar
31 |
32 | ';
33 | $response = new Response(200, [], $body);
34 | $result = ResponseConverter::convert($response, 'xml', 'simple_xml');
35 | $this->assertInstanceOf('\SimpleXMLElement', $result);
36 | }
37 |
38 | /**
39 | * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException
40 | */
41 | public function testConvertJsonToSimpleXml()
42 | {
43 | $body = '{"foo":"bar"}';
44 | $response = new Response(200, [], $body);
45 |
46 | ResponseConverter::convert($response, 'json', 'simple_xml');
47 | }
48 |
49 | /**
50 | * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException
51 | */
52 | public function testConvertXmlToArray()
53 | {
54 | $body = '
55 |
56 | foo
57 | bar
58 |
59 | ';
60 | $response = new Response(200, [], $body);
61 |
62 | ResponseConverter::convert($response, 'xml', 'array');
63 | }
64 |
65 | /**
66 | * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException
67 | */
68 | public function testConvertJsonToFoobar()
69 | {
70 | $body = '{"foo":"bar"}';
71 | $response = new Response(200, [], $body);
72 |
73 | ResponseConverter::convert($response, 'json', 'foobar');
74 | }
75 |
76 | public function testConvertToSimpleXml()
77 | {
78 | $body = '
79 |
80 | foo
81 | bar
82 |
83 | ';
84 |
85 | $response = new Response(200, [], $body);
86 | $result = ResponseConverter::convertToSimpleXml($response);
87 |
88 | $this->assertInstanceOf('\SimpleXMLElement', $result);
89 | $this->assertEquals('foo', $result->firstname);
90 | }
91 |
92 | /**
93 | * @expectedException \Happyr\LinkedIn\Exception\LinkedInTransferException
94 | */
95 | public function testConvertToSimpleXmlError()
96 | {
97 | $body = '{Foo: bar}';
98 |
99 | $response = new Response(200, [], $body);
100 | $result = ResponseConverter::convertToSimpleXml($response);
101 |
102 | $this->assertInstanceOf('\SimpleXMLElement', $result);
103 | $this->assertEquals('foo', $result->firstname);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/tests/Http/UrlGeneratorTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($expected, $gen->dropLinkedInParams($test));
19 |
20 | $test = 'code=foobar&baz=foo';
21 | $expected = '?baz=foo';
22 | $this->assertEquals($expected, $gen->dropLinkedInParams($test));
23 |
24 | $test = 'foo=bar&code=foobar';
25 | $expected = '?foo=bar';
26 | $this->assertEquals($expected, $gen->dropLinkedInParams($test));
27 |
28 | $test = 'code=foobar';
29 | $expected = '';
30 | $this->assertEquals($expected, $gen->dropLinkedInParams($test));
31 |
32 | $test = '';
33 | $expected = '';
34 | $this->assertEquals($expected, $gen->dropLinkedInParams($test));
35 |
36 | /* ----------------- */
37 |
38 | $test = 'foo=bar&code=';
39 | $expected = '?foo=bar';
40 | $this->assertEquals($expected, $gen->dropLinkedInParams($test));
41 |
42 | $test = 'code=';
43 | $expected = '';
44 | $this->assertEquals($expected, $gen->dropLinkedInParams($test));
45 |
46 | $test = 'foo=bar&code';
47 | $expected = '?foo=bar';
48 | $this->assertEquals($expected, $gen->dropLinkedInParams($test));
49 |
50 | $test = 'code';
51 | $expected = '';
52 | $this->assertEquals($expected, $gen->dropLinkedInParams($test));
53 | }
54 |
55 | public function testGetUrl()
56 | {
57 | $gen = new DummyUrlGenerator();
58 |
59 | $expected = 'https://api.linkedin.com/?bar=baz';
60 | $this->assertEquals($expected, $gen->getUrl('api', '', ['bar' => 'baz']), 'No path');
61 |
62 | $expected = 'https://api.linkedin.com/foobar';
63 | $this->assertEquals($expected, $gen->getUrl('api', 'foobar'), 'Path does not begin with forward slash');
64 | $this->assertEquals($expected, $gen->getUrl('api', '/foobar'), 'Path begins with forward slash');
65 |
66 | $expected = 'https://api.linkedin.com/foobar?bar=baz';
67 | $this->assertEquals($expected, $gen->getUrl('api', 'foobar', ['bar' => 'baz']), 'One parameter');
68 |
69 | $expected = 'https://api.linkedin.com/foobar?bar=baz&a=b&c=d';
70 | $this->assertEquals($expected, $gen->getUrl('api', 'foobar', ['bar' => 'baz', 'a' => 'b', 'c' => 'd']), 'Many parameters');
71 |
72 | $expected = 'https://api.linkedin.com/foobar?bar=baz%20a%20b';
73 | $notExpected = 'https://api.linkedin.com/foobar?bar=baz+a+b';
74 | $this->assertEquals($expected, $gen->getUrl('api', 'foobar', ['bar' => 'baz a b']), 'Use of PHP_QUERY_RFC3986');
75 | $this->assertNotEquals($notExpected, $gen->getUrl('api', 'foobar', ['bar' => 'baz a b']), 'Dont use PHP_QUERY_RFC1738');
76 | }
77 |
78 | public function testGetUrlWithParams()
79 | {
80 | $gen = new UrlGenerator();
81 |
82 | $expected = 'https://api.linkedin.com/endpoint?bar=baz&format=json';
83 | $this->assertEquals($expected, $gen->getUrl('api', 'endpoint?bar=baz', ['format' => 'json']));
84 |
85 | $expected = 'https://api.linkedin.com/endpoint?bar=baz&bar=baz';
86 | $this->assertEquals($expected, $gen->getUrl('api', 'endpoint?bar=baz', ['bar' => 'baz']));
87 | }
88 |
89 | public function testGetCurrentURL()
90 | {
91 | $gen = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator', ['getHttpProtocol', 'getHttpHost', 'dropLinkedInParams'], []);
92 | $gen->expects($this->any())->method('getHttpProtocol')->will($this->returnValue('http'));
93 | $gen->expects($this->any())->method('getHttpHost')->will($this->returnValue('www.test.com'));
94 | $gen->expects($this->any())->method('dropLinkedInParams')->will($this->returnCallback(function ($arg) {
95 | return empty($arg) ? '' : '?'.$arg;
96 | }));
97 |
98 | // fake the HPHP $_SERVER globals
99 | $_SERVER['REQUEST_URI'] = '/unit-tests.php?one=one&two=two&three=three';
100 | $this->assertEquals(
101 | 'http://www.test.com/unit-tests.php?one=one&two=two&three=three',
102 | $gen->getCurrentUrl(),
103 | 'getCurrentUrl function is changing the current URL');
104 |
105 | // ensure structure of valueless GET params is retained (sometimes
106 | // an = sign was present, and sometimes it was not)
107 | // first test when equal signs are present
108 | $_SERVER['REQUEST_URI'] = '/unit-tests.php?one=&two=&three=';
109 | $this->assertEquals(
110 | 'http://www.test.com/unit-tests.php?one=&two=&three=',
111 | $gen->getCurrentUrl(),
112 | 'getCurrentUrl function is changing the current URL');
113 |
114 | // now confirm that
115 | $_SERVER['REQUEST_URI'] = '/unit-tests.php?one&two&three';
116 | $this->assertEquals(
117 | 'http://www.test.com/unit-tests.php?one&two&three',
118 | $gen->getCurrentUrl(),
119 | 'getCurrentUrl function is changing the current URL'
120 | );
121 | }
122 |
123 | public function testGetCurrentURLPort80()
124 | {
125 | $gen = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator', ['getHttpProtocol', 'getHttpHost', 'dropLinkedInParams'], []);
126 | $gen->expects($this->any())->method('getHttpProtocol')->will($this->returnValue('http'));
127 | $gen->expects($this->any())->method('getHttpHost')->will($this->returnValue('www.test.com:80'));
128 | $gen->expects($this->any())->method('dropLinkedInParams')->will($this->returnCallback(function ($arg) {
129 | return empty($arg) ? '' : '?'.$arg;
130 | }));
131 |
132 | //test port 80
133 | $_SERVER['REQUEST_URI'] = '/foobar.php';
134 | $this->assertEquals(
135 | 'http://www.test.com/foobar.php',
136 | $gen->getCurrentUrl(),
137 | 'port 80 should not be shown'
138 | );
139 | }
140 |
141 | public function testGetCurrentURLPort8080()
142 | {
143 | $gen = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator', ['getHttpProtocol', 'getHttpHost', 'dropLinkedInParams'], []);
144 | $gen->expects($this->any())->method('getHttpProtocol')->will($this->returnValue('http'));
145 | $gen->expects($this->any())->method('getHttpHost')->will($this->returnValue('www.test.com:8080'));
146 | $gen->expects($this->any())->method('dropLinkedInParams')->will($this->returnCallback(function ($arg) {
147 | return empty($arg) ? '' : '?'.$arg;
148 | }));
149 |
150 | //test non default port 8080
151 | $_SERVER['REQUEST_URI'] = '/foobar.php';
152 | $this->assertEquals(
153 | 'http://www.test.com:8080/foobar.php',
154 | $gen->getCurrentUrl(),
155 | 'port 80 should not be shown'
156 | );
157 | }
158 |
159 | public function testHttpHost()
160 | {
161 | $real = 'foo.com';
162 | $_SERVER['HTTP_HOST'] = $real;
163 | $_SERVER['HTTP_X_FORWARDED_HOST'] = 'evil.com';
164 | $gen = new DummyUrlGenerator();
165 | $this->assertEquals($real, $gen->GetHttpHost());
166 | }
167 |
168 | public function testHttpProtocolApache()
169 | {
170 | $_SERVER['HTTPS'] = 'on';
171 | $gen = new DummyUrlGenerator();
172 | $this->assertEquals('https', $gen->GetHttpProtocol());
173 | }
174 |
175 | public function testHttpProtocolNginx()
176 | {
177 | $_SERVER['SERVER_PORT'] = '443';
178 | $gen = new DummyUrlGenerator();
179 | $this->assertEquals('https', $gen->GetHttpProtocol());
180 | }
181 |
182 | public function testHttpHostForwarded()
183 | {
184 | $real = 'foo.com';
185 | $_SERVER['HTTP_HOST'] = 'localhost';
186 | $_SERVER['HTTP_X_FORWARDED_HOST'] = $real;
187 | $gen = new DummyUrlGenerator();
188 | $gen->setTrustForwarded(true);
189 | $this->assertEquals($real, $gen->GetHttpHost());
190 | }
191 |
192 | public function testHttpProtocolForwarded()
193 | {
194 | $_SERVER['HTTPS'] = 'on';
195 | $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http';
196 | $gen = new DummyUrlGenerator();
197 | $gen->setTrustForwarded(true);
198 | $this->assertEquals('http', $gen->GetHttpProtocol());
199 | }
200 |
201 | public function testHttpProtocolForwardedSecure()
202 | {
203 | $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
204 | $gen = new DummyUrlGenerator();
205 | $this->assertEquals('http', $gen->GetHttpProtocol());
206 |
207 | $gen->setTrustForwarded(true);
208 | $this->assertEquals('https', $gen->GetHttpProtocol());
209 | }
210 |
211 | protected function tearDown()
212 | {
213 | unset($_SERVER['HTTPS']);
214 | unset($_SERVER['HTTP_X_FORWARDED_PROTO']);
215 | $_SERVER['HTTP_HOST'] = 'localhost';
216 | unset($_SERVER['HTTP_X_FORWARDED_HOST']);
217 | $_SERVER['SERVER_PORT'] = '80';
218 | $_SERVER['REQUEST_URI'] = '';
219 | }
220 | }
221 |
222 | class DummyUrlGenerator extends UrlGenerator
223 | {
224 | public function getHttpHost()
225 | {
226 | return parent::getHttpHost();
227 | }
228 |
229 | public function getHttpProtocol()
230 | {
231 | return parent::getHttpProtocol();
232 | }
233 |
234 | public function dropLinkedInParams($query)
235 | {
236 | return parent::dropLinkedInParams($query);
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/tests/LinkedInTest.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class LinkedInTest extends \PHPUnit_Framework_TestCase
11 | {
12 | const APP_ID = '123456789';
13 | const APP_SECRET = '987654321';
14 |
15 | public function testApi()
16 | {
17 | $resource = 'resource';
18 | $token = 'token';
19 | $urlParams = ['url' => 'foo'];
20 | $postParams = ['post' => 'bar'];
21 | $method = 'GET';
22 | $expected = ['foobar' => 'test'];
23 | $response = new Response(200, [], json_encode($expected));
24 | $url = 'http://example.com/test';
25 |
26 | $headers = ['Authorization' => 'Bearer '.$token, 'Content-Type' => 'application/json', 'x-li-format' => 'json'];
27 |
28 | $generator = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator', ['getUrl']);
29 | $generator->expects($this->once())->method('getUrl')->with(
30 | $this->equalTo('api'),
31 | $this->equalTo($resource),
32 | $this->equalTo([
33 | 'url' => 'foo',
34 | 'format' => 'json',
35 | ]))
36 | ->willReturn($url);
37 |
38 | $requestManager = $this->getMock('Happyr\LinkedIn\Http\RequestManager', ['sendRequest']);
39 | $requestManager->expects($this->once())->method('sendRequest')->with(
40 | $this->equalTo($method),
41 | $this->equalTo($url),
42 | $this->equalTo($headers),
43 | $this->equalTo(json_encode($postParams)))
44 | ->willReturn($response);
45 |
46 | $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['getAccessToken', 'getUrlGenerator', 'getRequestManager'], [self::APP_ID, self::APP_SECRET]);
47 |
48 | $linkedIn->expects($this->once())->method('getAccessToken')->willReturn($token);
49 | $linkedIn->expects($this->once())->method('getUrlGenerator')->willReturn($generator);
50 | $linkedIn->expects($this->once())->method('getRequestManager')->willReturn($requestManager);
51 |
52 | $result = $linkedIn->api($method, $resource, ['query' => $urlParams, 'json' => $postParams]);
53 | $this->assertEquals($expected, $result);
54 | }
55 |
56 | public function testIsAuthenticated()
57 | {
58 | $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['getAccessToken'], [self::APP_ID, self::APP_SECRET]);
59 | $linkedIn->expects($this->once())->method('getAccessToken')->willReturn(null);
60 | $this->assertFalse($linkedIn->isAuthenticated());
61 |
62 | $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['api', 'getAccessToken'], [self::APP_ID, self::APP_SECRET]);
63 | $linkedIn->expects($this->once())->method('getAccessToken')->willReturn('token');
64 | $linkedIn->expects($this->once())->method('api')->willReturn(['id' => 4711]);
65 | $this->assertTrue($linkedIn->isAuthenticated());
66 |
67 | $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['api', 'getAccessToken'], [self::APP_ID, self::APP_SECRET]);
68 | $linkedIn->expects($this->once())->method('getAccessToken')->willReturn('token');
69 | $linkedIn->expects($this->once())->method('api')->willReturn(['foobar' => 4711]);
70 | $this->assertFalse($linkedIn->isAuthenticated());
71 | }
72 |
73 | /**
74 | * Test a call to getAccessToken when there is no token.
75 | */
76 | public function testAccessTokenAccessors()
77 | {
78 | $token = 'token';
79 |
80 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['fetchNewAccessToken'], [], '', false);
81 | $auth->expects($this->once())->method('fetchNewAccessToken')->will($this->returnValue($token));
82 |
83 | $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['getAuthenticator'], [], '', false);
84 | $linkedIn->expects($this->once())->method('getAuthenticator')->willReturn($auth);
85 |
86 | // Make sure we go to the authenticator only once
87 | $this->assertEquals($token, $linkedIn->getAccessToken());
88 | $this->assertEquals($token, $linkedIn->getAccessToken());
89 | }
90 |
91 | public function testGeneratorAccessors()
92 | {
93 | $get = new \ReflectionMethod('Happyr\LinkedIn\LinkedIn', 'getUrlGenerator');
94 | $get->setAccessible(true);
95 | $linkedIn = new LinkedIn(self::APP_ID, self::APP_SECRET);
96 |
97 | // test default
98 | $this->assertInstanceOf('Happyr\LinkedIn\Http\UrlGenerator', $get->invoke($linkedIn));
99 |
100 | $object = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator');
101 | $linkedIn->setUrlGenerator($object);
102 | $this->assertEquals($object, $get->invoke($linkedIn));
103 | }
104 |
105 | public function testHasError()
106 | {
107 | $linkedIn = new LinkedIn(self::APP_ID, self::APP_SECRET);
108 |
109 | unset($_GET['error']);
110 | $this->assertFalse($linkedIn->hasError());
111 |
112 | $_GET['error'] = 'foobar';
113 | $this->assertTrue($linkedIn->hasError());
114 | }
115 |
116 | public function testGetError()
117 | {
118 | $linkedIn = new LinkedIn(self::APP_ID, self::APP_SECRET);
119 |
120 | unset($_GET['error']);
121 | unset($_GET['error_description']);
122 |
123 | $this->assertNull($linkedIn->getError());
124 |
125 | $_GET['error'] = 'foo';
126 | $_GET['error_description'] = 'bar';
127 |
128 | $this->assertEquals('foo', $linkedIn->getError()->getName());
129 | $this->assertEquals('bar', $linkedIn->getError()->getDescription());
130 | }
131 |
132 | public function testGetErrorWithMissingDescription()
133 | {
134 | $linkedIn = new LinkedIn(self::APP_ID, self::APP_SECRET);
135 |
136 | unset($_GET['error']);
137 | unset($_GET['error_description']);
138 |
139 | $_GET['error'] = 'foo';
140 |
141 | $this->assertEquals('foo', $linkedIn->getError()->getName());
142 | $this->assertNull($linkedIn->getError()->getDescription());
143 | }
144 |
145 | public function testFormatAccessors()
146 | {
147 | $get = new \ReflectionMethod('Happyr\LinkedIn\LinkedIn', 'getFormat');
148 | $get->setAccessible(true);
149 | $linkedIn = new LinkedIn(self::APP_ID, self::APP_SECRET);
150 |
151 | //test default
152 | $this->assertEquals('json', $get->invoke($linkedIn));
153 |
154 | $format = 'foo';
155 | $linkedIn->setFormat($format);
156 | $this->assertEquals($format, $get->invoke($linkedIn));
157 | }
158 |
159 | public function testLoginUrl()
160 | {
161 | $currentUrl = 'currentUrl';
162 | $loginUrl = 'result';
163 |
164 | $generator = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator', ['getCurrentUrl']);
165 | $generator->expects($this->once())->method('getCurrentUrl')->willReturn($currentUrl);
166 |
167 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getLoginUrl'], [], '', false);
168 | $auth->expects($this->once())->method('getLoginUrl')
169 | ->with($generator, ['redirect_uri' => $currentUrl])
170 | ->will($this->returnValue($loginUrl));
171 |
172 | $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['getAuthenticator', 'getUrlGenerator'], [], '', false);
173 | $linkedIn->expects($this->once())->method('getAuthenticator')->willReturn($auth);
174 | $linkedIn->expects($this->once())->method('getUrlGenerator')->willReturn($generator);
175 |
176 | $linkedIn->getLoginUrl();
177 | }
178 |
179 | public function testLoginUrlWithParameter()
180 | {
181 | $loginUrl = 'result';
182 | $otherUrl = 'otherUrl';
183 |
184 | $generator = $this->getMock('Happyr\LinkedIn\Http\UrlGenerator');
185 |
186 | $auth = $this->getMock('Happyr\LinkedIn\Authenticator', ['getLoginUrl'], [], '', false);
187 | $auth->expects($this->once())->method('getLoginUrl')
188 | ->with($generator, ['redirect_uri' => $otherUrl])
189 | ->will($this->returnValue($loginUrl));
190 |
191 | $linkedIn = $this->getMock('Happyr\LinkedIn\LinkedIn', ['getAuthenticator', 'getUrlGenerator'], [], '', false);
192 | $linkedIn->expects($this->once())->method('getAuthenticator')->willReturn($auth);
193 | $linkedIn->expects($this->once())->method('getUrlGenerator')->willReturn($generator);
194 |
195 | $linkedIn->getLoginUrl(['redirect_uri' => $otherUrl]);
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/tests/Storage/IlluminateSessionStorageTest.php:
--------------------------------------------------------------------------------
1 | storage = new IlluminateSessionStorage();
24 | }
25 |
26 | public function testSet()
27 | {
28 | Session::shouldReceive('put')->once()->with($this->prefix.'code', 'foobar');
29 |
30 | $this->storage->set('code', 'foobar');
31 | }
32 |
33 | /**
34 | * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException
35 | */
36 | public function testSetFail()
37 | {
38 | $this->storage->set('foobar', 'baz');
39 | }
40 |
41 | public function testGet()
42 | {
43 | $expected = 'foobar';
44 | Session::shouldReceive('get')->once()->with($this->prefix.'code')->andReturn($expected);
45 | $result = $this->storage->get('code');
46 | $this->assertEquals($expected, $result);
47 |
48 | Session::shouldReceive('get')->once()->with($this->prefix.'state')->andReturn(null);
49 | $result = $this->storage->get('state');
50 | $this->assertNull($result);
51 | }
52 |
53 | public function testClear()
54 | {
55 | Session::shouldReceive('forget')->once()->with($this->prefix.'code')->andReturn(true);
56 | $this->storage->clear('code');
57 | }
58 |
59 | /**
60 | * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException
61 | */
62 | public function testClearFail()
63 | {
64 | $this->storage->clear('foobar');
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Storage/SessionStorageTest.php:
--------------------------------------------------------------------------------
1 | storage = new SessionStorage();
24 | }
25 |
26 | public function testSet()
27 | {
28 | $this->storage->set('code', 'foobar');
29 | $this->assertEquals($_SESSION[$this->prefix.'code'], 'foobar');
30 | }
31 |
32 | /**
33 | * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException
34 | */
35 | public function testSetFail()
36 | {
37 | $this->storage->set('foobar', 'baz');
38 | }
39 |
40 | public function testGet()
41 | {
42 | unset($_SESSION[$this->prefix.'state']);
43 | $result = $this->storage->get('state');
44 | $this->assertNull($result);
45 |
46 | $expected = 'foobar';
47 | $_SESSION[$this->prefix.'code'] = $expected;
48 | $result = $this->storage->get('code');
49 | $this->assertEquals($expected, $result);
50 | }
51 |
52 | public function testClear()
53 | {
54 | $_SESSION[$this->prefix.'code'] = 'foobar';
55 | $this->storage->clear('code');
56 | $this->assertFalse(isset($_SESSION[$this->prefix.'code']));
57 | }
58 |
59 | /**
60 | * @expectedException \Happyr\LinkedIn\Exception\InvalidArgumentException
61 | */
62 | public function testClearFail()
63 | {
64 | $this->storage->clear('foobar');
65 | }
66 |
67 | public function testClearAll()
68 | {
69 | $validKeys = SessionStorage::$validKeys;
70 |
71 | $storage = m::mock('Happyr\LinkedIn\Storage\SessionStorage[clear]')
72 | ->shouldReceive('clear')->times(count($validKeys))
73 | ->with(m::on(function ($arg) use ($validKeys) {
74 | return in_array($arg, $validKeys);
75 | }))
76 | ->getMock();
77 |
78 | $storage->clearAll();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------