diff --git a/README.md b/README.md index 2eebab78..420dc194 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ Included service implementations - Delicious - Deezer - DeviantArt + - Docusign - Dropbox - Eve Online - Facebook diff --git a/examples/docusign.php b/examples/docusign.php new file mode 100644 index 00000000..3130295c --- /dev/null +++ b/examples/docusign.php @@ -0,0 +1,52 @@ + + * @copyright Copyright (c) 2017 The authors + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +use OAuth\OAuth2\Service\Docusign; +use OAuth\Common\Storage\Session; +use OAuth\Common\Consumer\Credentials; + +/** + * Bootstrap the example + */ +require_once __DIR__ . '/bootstrap.php'; + +// Session storage +$storage = new Session(); + +// Setup the credentials for the requests +$credentials = new Credentials( + $servicesCredentials['docusign']['key'], + $servicesCredentials['docusign']['secret'], + $currentUri->getAbsoluteUri() +); + +// Instantiate the Docusign service using the credentials, http client, storage mechanism for the token and profile scope +/** @var $docusignService Docusign */ +$docusignService = $serviceFactory->createService('docusign', $credentials, $storage, array('signature')); + +if (!empty($_GET['code'])) { + // This was a callback request from Docusign, get the token + $token = $docusignService->requestAccessToken($_GET['code']); + + // Send a request with it + $result = json_decode($docusignService->request('/oauth/userinfo'), true); + + // Show some of the resultant data + echo 'Your unique Docusign user id is: ' . $result['accounts'][0]['account_id'] . ' and your name is ' . $result['name']; + +} elseif (!empty($_GET['go']) && $_GET['go'] === 'go') { + $url = $docusignService->getAuthorizationUri(); + header('Location: ' . $url); +} else { + $url = $currentUri->getRelativeUri() . '?go=go'; + echo "Login with Docusign!"; +} \ No newline at end of file diff --git a/examples/init.example.php b/examples/init.example.php index 6ab1cdb0..dde4789b 100644 --- a/examples/init.example.php +++ b/examples/init.example.php @@ -59,6 +59,10 @@ 'key' => '', 'secret' => '', ), + 'docusign' => array( + 'key' => '', + 'secret' => '', + ), 'dropbox' => array( 'key' => '', 'secret' => '', diff --git a/src/OAuth/OAuth2/Service/Docusign.php b/src/OAuth/OAuth2/Service/Docusign.php new file mode 100644 index 00000000..782204c6 --- /dev/null +++ b/src/OAuth/OAuth2/Service/Docusign.php @@ -0,0 +1,123 @@ + + * @link https://images-na.ssl-images-docusign.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf + */ +class Docusign extends AbstractService +{ + /** + * Defined scopes + * @link https://images-na.ssl-images-docusign.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf + */ + const SCOPE_SIGNATURE = 'signature'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + // account-d.docusign.com for the developer sandbox + // account.docusign.com for the production platform. + $this->baseApiUri = new Uri('https://account-d.docusign.com'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $parameters['scope'] = implode(' ', $this->scopes); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://account-d.docusign.com/oauth/auth'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://account-d.docusign.com/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error_description'] . '"'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/tests/Unit/OAuth2/Service/Docusign.php b/tests/Unit/OAuth2/Service/Docusign.php new file mode 100644 index 00000000..094fef4d --- /dev/null +++ b/tests/Unit/OAuth2/Service/Docusign.php @@ -0,0 +1,207 @@ +getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertInstanceOf('\\OAuth\\OAuth2\\Service\\ServiceInterface', $service); + } + + /** + * @covers OAuth\OAuth2\Service\Docusign::__construct + */ + public function testConstructCorrectInstanceWithoutCustomUri() + { + $service = new Docusign( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertInstanceOf('\\OAuth\\OAuth2\\Service\\AbstractService', $service); + } + + /** + * @covers OAuth\OAuth2\Service\Docusign::__construct + */ + public function testConstructCorrectInstanceWithCustomUri() + { + $service = new Docusign( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface'), + array(), + $this->getMock('\\OAuth\\Common\\Http\\Uri\\UriInterface') + ); + + $this->assertInstanceOf('\\OAuth\\OAuth2\\Service\\AbstractService', $service); + } + + /** + * @covers OAuth\OAuth2\Service\Docusign::__construct + * @covers OAuth\OAuth2\Service\Docusign::getAuthorizationEndpoint + */ + public function testGetAuthorizationEndpoint() + { + $service = new Docusign( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertSame('https://account-d.docusign.com/oauth/auth', $service->getAuthorizationEndpoint()->getAbsoluteUri()); + } + + /** + * @covers OAuth\OAuth2\Service\Docusign::__construct + * @covers OAuth\OAuth2\Service\Docusign::getAccessTokenEndpoint + */ + public function testGetAccessTokenEndpoint() + { + $service = new Docusign( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertSame('https://account-d.docusign.com/oauth/token', $service->getAccessTokenEndpoint()->getAbsoluteUri()); + } + + /** + * @covers OAuth\OAuth2\Service\Docusign::__construct + * @covers OAuth\OAuth2\Service\Docusign::getAuthorizationMethod + */ + public function testGetAuthorizationMethod() + { + $client = $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects($this->once())->method('retrieveResponse')->will($this->returnArgument(2)); + + $token = $this->getMock('\\OAuth\\OAuth2\\Token\\TokenInterface'); + $token->expects($this->once())->method('getEndOfLife')->will($this->returnValue(TokenInterface::EOL_NEVER_EXPIRES)); + $token->expects($this->once())->method('getAccessToken')->will($this->returnValue('foo')); + + $storage = $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface'); + $storage->expects($this->once())->method('retrieveAccessToken')->will($this->returnValue($token)); + + $service = new Docusign( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $storage + ); + + $headers = $service->request('https://pieterhordijk.com/my/awesome/path'); + + $this->assertTrue(array_key_exists('Authorization', $headers)); + $this->assertTrue(in_array('Bearer foo', $headers, true)); + } + + /** + * @covers OAuth\OAuth2\Service\Docusign::__construct + * @covers OAuth\OAuth2\Service\Docusign::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseThrowsExceptionOnNulledResponse() + { + $client = $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects($this->once())->method('retrieveResponse')->will($this->returnValue(null)); + + $service = new Docusign( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->setExpectedException('\\OAuth\\Common\\Http\\Exception\\TokenResponseException'); + + $service->requestAccessToken('foo'); + } + + /** + * @covers OAuth\OAuth2\Service\Docusign::__construct + * @covers OAuth\OAuth2\Service\Docusign::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseThrowsExceptionOnErrorDescription() + { + $client = $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects($this->once())->method('retrieveResponse')->will($this->returnValue('error_description=some_error')); + + $service = new Docusign( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->setExpectedException('\\OAuth\\Common\\Http\\Exception\\TokenResponseException'); + + $service->requestAccessToken('foo'); + } + + /** + * @covers OAuth\OAuth2\Service\Docusign::__construct + * @covers OAuth\OAuth2\Service\Docusign::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseThrowsExceptionOnError() + { + $client = $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects($this->once())->method('retrieveResponse')->will($this->returnValue('error=some_error')); + + $service = new Docusign( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->setExpectedException('\\OAuth\\Common\\Http\\Exception\\TokenResponseException'); + + $service->requestAccessToken('foo'); + } + + /** + * @covers OAuth\OAuth2\Service\Docusign::__construct + * @covers OAuth\OAuth2\Service\Docusign::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseValidWithoutRefreshToken() + { + $client = $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects($this->once())->method('retrieveResponse')->will($this->returnValue('{"access_token":"foo","expires_in":"bar"}')); + + $service = new Docusign( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertInstanceOf('\\OAuth\\OAuth2\\Token\\StdOAuth2Token', $service->requestAccessToken('foo')); + } + + /** + * @covers OAuth\OAuth2\Service\Docusign::__construct + * @covers OAuth\OAuth2\Service\Docusign::parseAccessTokenResponse + */ + public function testParseAccessTokenResponseValidWithRefreshToken() + { + $client = $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'); + $client->expects($this->once())->method('retrieveResponse')->will($this->returnValue('{"access_token":"foo","expires_in":"bar","refresh_token":"baz"}')); + + $service = new Docusign( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $client, + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertInstanceOf('\\OAuth\\OAuth2\\Token\\StdOAuth2Token', $service->requestAccessToken('foo')); + } +}