Skip to content

Commit

Permalink
Merge pull request #17 from Barnetik/master
Browse files Browse the repository at this point in the history
Added Azure Token Provider
  • Loading branch information
arraintxo authored Jan 30, 2017
2 parents f6b5249 + 1b3de3c commit 291b91e
Show file tree
Hide file tree
Showing 4 changed files with 438 additions and 2 deletions.
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,28 @@ Then using the Composer binary:

This library uses the Buzz browser to make calls to the [Microsoft Translator V2 API](http://msdn.microsoft.com/en-us/library/ff512419.aspx).

You need to [obtain a Microsoft Azure Cognitive Services subscription Key](http://docs.microsofttranslator.com/text-translate.html). This can be used to instantiate the ``AzureTokenProvider``:

```php
<?php

use Buzz\Browser;
use MatthiasNoback\MicrosoftOAuth\AzureTokenProvider;
use MatthiasNoback\MicrosoftTranslator\MicrosoftTranslator;

$browser = new Browser();

$azureKey = '[YOUR-AZURE-SUBSCRIPTION-KEY]';

$accessTokenProvider = new AzureTokenProvider($browser, $azureKey);

$translator = new MicrosoftTranslator($browser, $accessTokenProvider);
```

## Azure DataMarket token usage [deprecated]

You need to register your application at the [Azure DataMarket](https://datamarket.azure.com/developer/applications) and
thereby retrieve a "client id" and a "client secret". These kan be used to instantiate the ``AccessTokenProvider`` on which
thereby retrieve a "client id" and a "client secret". These can be used to instantiate the ``AccessTokenProvider`` (deprecated) on which
the ``MicrosoftTranslator`` depends:

```php
Expand All @@ -43,6 +63,7 @@ $accessTokenProvider = new AccessTokenProvider($browser, $clientId, $clientSecre
$translator = new MicrosoftTranslator($browser, $accessTokenProvider);
```


### Optional: enable the access token cache

Each call to the translator service is preceded by a call to Microsoft's OAuth server. Each access token however, may be
Expand Down Expand Up @@ -120,4 +141,4 @@ There is also a [MicrosoftTranslatorServiceProvider](https://github.com/matthias

## TODO

There are some more calls to be implemented, and also some more tests to be added.
There are some more calls to be implemented, and also some more tests to be added.
88 changes: 88 additions & 0 deletions src/MatthiasNoback/MicrosoftOAuth/AzureTokenProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace MatthiasNoback\MicrosoftOAuth;

use Buzz\Browser;
use MatthiasNoback\Exception\RequestFailedException;
use MatthiasNoback\Exception\InvalidResponseException;

class AzureTokenProvider implements AccessTokenProviderInterface
{
const AUTH_URL = 'https://api.cognitive.microsoft.com/sts/v1.0/issueToken';

/**
* @var \Buzz\Browser
*/
private $browser;

/**
* @var string The client id of your application
*/
private $azureKey;

/**
* @var AccessTokenCacheInterface|null
*/
private $accessTokenCache;

/**
* The AccessTokenProvider requires a Buzz browser instance, and both a client id
* and a client secret. You can obtain these by registering your application
* at https://datamarket.azure.com/developer/applications
*
* @param \Buzz\Browser $browser The browser to use for fetching access tokens
* @param string $azureKey The azure key for Translator service
*/
public function __construct(Browser $browser, $azureKey)
{
$this->browser = $browser;
$this->azureKey = $azureKey;
}

public function setCache(AccessTokenCacheInterface $accessTokenCache)
{
$this->accessTokenCache = $accessTokenCache;
}

public function getAccessToken($scope, $grantType)
{
if ($this->accessTokenCache !== null && $this->accessTokenCache->has($scope, $grantType)) {
return $this->accessTokenCache->get($scope, $grantType);
}

$accessToken = $this->authorize($scope, $grantType);

if ($this->accessTokenCache !== null) {
$this->accessTokenCache->set($scope, $grantType, $accessToken);
}

return $accessToken;
}

private function authorize($scope, $grantType)
{
try {
$response = $this->browser->post(
self::AUTH_URL . "?Subscription-Key=" . urlencode($this->azureKey),
array('Content-Length' => 0)
);
}
catch (\Exception $previous) {
throw new RequestFailedException(sprintf(
'Request failed: %s',
$previous->getMessage()
), null, $previous);
}

if (!$response->isSuccessful()) {
throw new RequestFailedException(sprintf(
'Call to Auth server failed, %d: %s',
$response->getStatusCode(),
$response->getReasonPhrase()
));
}

/* @var $response \Buzz\Message\Response */
return $response->getContent();
}
}
134 changes: 134 additions & 0 deletions tests/MatthiasNoback/MicrosoftOAuth/AzureTokenProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

namespace MatthiasNoback\Tests\MicrosoftOAuth;

use MatthiasNoback\MicrosoftOAuth\AzureTokenProvider;

class AzuresTokenProviderTest extends \PHPUnit_Framework_TestCase
{
public function testGetTokenWithoutCache()
{
$azureKey = 'azureKey';
$accessToken = 'accessToken';
$scope = 'theScope';
$grantType = 'theGrantType';

$response = $this->createMockResponse($accessToken);

$browser = $this->createMockBrowser();
$browser
->expects($this->once())
->method('post')
->with(
'https://api.cognitive.microsoft.com/sts/v1.0/issueToken?Subscription-Key=' . $azureKey,
array('Content-Length' => 0)
)
->will($this->returnValue($response));
$accessTokenProvider = new AzureTokenProvider($browser, $azureKey);

$actualAccessToken = $accessTokenProvider->getAccessToken($scope, $grantType);

$this->assertSame($accessToken, $actualAccessToken);
}

public function testGetTokenWithCacheMiss()
{
$scope = 'theScope';
$grantType = 'theGrantType';

$accessToken = 'accessToken';

// the cache does not contain an access token yet
$accessTokenCache = $this->createMockAccessTokenCache();
$accessTokenCache
->expects($this->once())
->method('has')
->with($scope, $grantType)
->will($this->returnValue(false));

// the browser will used to fetch a fresh access token
$response = $this->createMockResponse($accessToken);
$browser = $this->createMockBrowser();
$browser
->expects($this->once())
->method('post')
->will($this->returnValue($response));

// finally, the access token should be stored in the cache
$accessTokenCache
->expects($this->once())
->method('set')
->with($scope, $grantType, $accessToken);


$accessTokenProvider = new AzureTokenProvider($browser, 'azureKey');
$accessTokenProvider->setCache($accessTokenCache);

$actualAccessToken = $accessTokenProvider->getAccessToken($scope, $grantType);

$this->assertSame($accessToken, $actualAccessToken);
}

public function testGetTokenWithCacheHit()
{
$scope = 'theScope';
$grantType = 'theGrantType';

$accessToken = 'accessToken';

// the cache already contains an access token
$accessTokenCache = $this->createMockAccessTokenCache();
$accessTokenCache
->expects($this->once())
->method('has')
->with($scope, $grantType)
->will($this->returnValue(true));

// the browser should not be used
$browser = $this->createMockBrowser();
$browser
->expects($this->never())
->method('post');

// the access token will be retrieved from the cache
$accessTokenCache
->expects($this->once())
->method('get')
->with($scope, $grantType)
->will($this->returnValue($accessToken));

$accessTokenProvider = new AzureTokenProvider($browser, 'azureKey');
$accessTokenProvider->setCache($accessTokenCache);

$accessTokenProvider->getAccessToken($scope, $grantType);
}

private function createMockBrowser()
{
return $this
->getMockBuilder('Buzz\\Browser')
->disableOriginalConstructor()
->getMock();
}

private function createMockResponse($content)
{
$response = $this->getMock('Buzz\Message\Response');
$response
->expects($this->any())
->method('getContent')
->will($this->returnValue($content));

$response
->expects($this->any())
->method('isSuccessful')
->will($this->returnValue(true));

return $response;
}

private function createMockAccessTokenCache()
{
return $this->getMock('MatthiasNoback\MicrosoftOAuth\AccessTokenCacheInterface');
}
}
Loading

0 comments on commit 291b91e

Please sign in to comment.