diff --git a/README.md b/README.md index 2eebab78..c23d5d09 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Included service implementations - LinkedIn - Mailchimp - Microsoft + - Microsoft Graph - Mondo - Nest - Netatmo diff --git a/composer.lock b/composer.lock index 42ceafef..5f941acb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,59 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "734ee27aca2b4b8a33857520f518ef0c", - "packages": [], + "content-hash": "4707edb722c13e038d3a84b1c38c2b64", + "packages": [ + { + "name": "vlucas/phpdotenv", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2018-07-29T20:33:41+00:00" + } + ], "packages-dev": [ { "name": "phpunit/php-code-coverage", @@ -66,7 +117,7 @@ "testing", "xunit" ], - "time": "2014-09-02 10:13:14" + "time": "2014-09-02T10:13:14+00:00" }, { "name": "phpunit/php-file-iterator", @@ -113,7 +164,7 @@ "filesystem", "iterator" ], - "time": "2015-06-21 13:08:43" + "time": "2015-06-21T13:08:43+00:00" }, { "name": "phpunit/php-text-template", @@ -154,7 +205,7 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", @@ -195,7 +246,7 @@ "keywords": [ "timer" ], - "time": "2015-06-21 08:01:12" + "time": "2015-06-21T08:01:12+00:00" }, { "name": "phpunit/php-token-stream", @@ -245,7 +296,7 @@ "keywords": [ "tokenizer" ], - "time": "2014-03-03 05:10:30" + "time": "2014-03-03T05:10:30+00:00" }, { "name": "phpunit/phpunit", @@ -318,7 +369,7 @@ "testing", "xunit" ], - "time": "2014-10-17 09:04:17" + "time": "2014-10-17T09:04:17+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -367,7 +418,7 @@ "mock", "xunit" ], - "time": "2013-01-13 10:24:48" + "time": "2013-01-13T10:24:48+00:00" }, { "name": "predis/predis", @@ -491,19 +542,19 @@ "phpcs", "standards" ], - "time": "2015-09-09 00:18:50" + "time": "2015-09-09T00:18:50+00:00" }, { "name": "symfony/http-foundation", "version": "v2.7.4", "source": { "type": "git", - "url": "https://github.com/symfony/HttpFoundation.git", + "url": "https://github.com/symfony/http-foundation.git", "reference": "7253c2041652353e71560bbd300d6256d170ddaf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/7253c2041652353e71560bbd300d6256d170ddaf", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7253c2041652353e71560bbd300d6256d170ddaf", "reference": "7253c2041652353e71560bbd300d6256d170ddaf", "shasum": "" }, @@ -544,7 +595,7 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2015-08-27 06:45:45" + "time": "2015-08-27T06:45:45+00:00" }, { "name": "symfony/yaml", @@ -593,7 +644,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-08-24 07:13:45" + "time": "2015-08-24T07:13:45+00:00" } ], "aliases": [], diff --git a/examples/bootstrap.php b/examples/bootstrap.php index f02da414..3d43d28c 100644 --- a/examples/bootstrap.php +++ b/examples/bootstrap.php @@ -18,10 +18,10 @@ /** * Create a new instance of the URI class with the current URI, stripping the query string - */ + $uriFactory = new \OAuth\Common\Http\Uri\UriFactory(); $currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER); -$currentUri->setQuery(''); +$currentUri->setQuery('');*/ /** * Load the credential for the different services diff --git a/examples/init.example.php b/examples/init.example.php index 6ab1cdb0..2aadd85c 100644 --- a/examples/init.example.php +++ b/examples/init.example.php @@ -206,6 +206,11 @@ 'key' => '', 'secret' => '' ), + 'microsoft_grapht' => array( + 'CLIENT_ID' => '', + 'SECRET_CLIENT' => '', + 'CALLBACK_URL' => '' + ) ); /** @var $serviceFactory \OAuth\ServiceFactory An OAuth service factory. */ diff --git a/examples/microsoftGraph.php b/examples/microsoftGraph.php new file mode 100644 index 00000000..65ab6bc9 --- /dev/null +++ b/examples/microsoftGraph.php @@ -0,0 +1,38 @@ +setHttpClient(new CurlClient); +$microsofttGraphtnService = $serviceFactory->createService('microsoftgraph', $credentials, $storage); + + +if (!empty($_GET['code'])) { + // Obtiene el token a partir del codigo que se obtiene del callback + $token = $microsofttGraphtnService->requestAccessToken($_GET['code']); + + // Send a request para obtener las licencias que tiene el tenant + $result = json_decode($microsofttGraphtnService->request('/subscribedSkus?api-version=1.6'), true); + + // Se muestra el resultado + echo "
";
+    var_dump($result);
+    echo "
"; + +} else { + $url = $microsofttGraphtnService->getAuthorizationUri(); + header('Location: ' . $url); +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..e3e3e85a --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,45 @@ + + + + + tests/Unit + + + + + src/ + + src/OAuth/bootstrap.php + src/OAuth/Common/Exception/Exception.php + src/OAuth/Common/Http/Exception/TokenResponseException.php + src/OAuth/Common/Storage/Exception/StorageException.php + src/OAuth/Common/Storage/Exception/TokenNotFoundException.php + src/OAuth/Common/Token/Exception/ExpiredTokenException.php + src/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php + src/OAuth/OAuth2/Service/Exception/InvalidScopeException.php + src/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php + src/OAuth/OAuth2/Token/StdOAuth2Token.php + + + + + + + + diff --git a/src/OAuth/Common/Http/Client/CurlClient.php b/src/OAuth/Common/Http/Client/CurlClient.php index eae1be3e..04045425 100644 --- a/src/OAuth/Common/Http/Client/CurlClient.php +++ b/src/OAuth/Common/Http/Client/CurlClient.php @@ -87,13 +87,13 @@ public function retrieveResponse( curl_setopt($ch, CURLOPT_URL, $endpoint->getAbsoluteUri()); - if ($method === 'POST' || $method === 'PUT') { + if ($method === 'POST' || $method === 'PUT' || $method === 'PATCH') { if ($requestBody && is_array($requestBody)) { $requestBody = http_build_query($requestBody, '', '&'); } - if ($method === 'PUT') { - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + if ($method === 'PUT' || $method === 'PATCH') { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); } else { curl_setopt($ch, CURLOPT_POST, true); } @@ -102,7 +102,6 @@ public function retrieveResponse( } else { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); } - if ($this->maxRedirects > 0) { curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_MAXREDIRS, $this->maxRedirects); diff --git a/src/OAuth/OAuth2/Service/MicrosoftGraph.php b/src/OAuth/OAuth2/Service/MicrosoftGraph.php new file mode 100644 index 00000000..2bd78fe2 --- /dev/null +++ b/src/OAuth/OAuth2/Service/MicrosoftGraph.php @@ -0,0 +1,137 @@ + + */ +class MicrosoftGraph extends AbstractService +{ + private $resources = array( + 'graph' => 'https://graph.windows.net/' + ); + public function __construct(CredentialsInterface $credentials, ClientInterface $httpClient, TokenStorageInterface $storage, array $scopes = array(), UriInterface $baseApiUri = null, $stateParameterInAutUrl = false, $apiVersion = "") + { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, $stateParameterInAutUrl, $apiVersion); + if(is_null($baseApiUri)){ + $this->baseApiUri = new Uri($this->resources['graph'].'myorganization/'); + } + } + + /** + * Parses the access token response and returns a TokenInterface. + * + * + * @param string $responseBody + * + * @return TokenInterface + * + * @throws TokenResponseException + */ + 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; + } + + /** + * Returns the authorization API endpoint. + * + * @return UriInterface + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://login.microsoftonline.com/common/oauth2/authorize'); + } + + /** + * Returns the access token API endpoint. + * + * @return UriInterface + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://login.microsoftonline.com/common/oauth2/token'); + } + + /** + * @param $code + * @param null $state + * @return TokenInterface + * @throws Exception\InvalidAuthorizationStateException + * @throws TokenResponseException + */ + public function requestAccessToken($code, $state = null) + { + if (null !== $state) { + $this->validateAuthorizationState($state); + } + $bodyParams = array( + 'code' => $code, + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'grant_type' => 'authorization_code', + 'resource' => $this->resources['graph'] + ); + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + return $token; + } + + /** + * Devuelve los heades que tendra obtener el AccessToken + * @return array + */ + protected function getExtraOAuthHeaders() + { + return array( + 'Content-Type'=>'application/x-www-form-urlencoded' + ); + } + + /** + * Returns a class constant from ServiceInterface define el metodo e autorizacion del API + * Header is the sane default. + * + * @return int + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } +} \ No newline at end of file diff --git a/tests/Unit/OAuth2/Service/MicrosoftGraphTest.php b/tests/Unit/OAuth2/Service/MicrosoftGraphTest.php new file mode 100644 index 00000000..3c7c8100 --- /dev/null +++ b/tests/Unit/OAuth2/Service/MicrosoftGraphTest.php @@ -0,0 +1,92 @@ + + */ +class MicrosoftGraphTest extends \PHPUnit_Framework_TestCase +{ + + /** + * Test para correcta construccion con interfaces + */ + public function testConstructCorrectInterfaceWithoutCustomUri() + { + $service = new MicrosoftGraph( + $this->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); + } + + /** + * Test para correcta construccion de la instancia como servicio abstracto + */ + public function testConstructCorrectInstanceWithoutCustomUri() + { + $service = new MicrosoftGraph( + $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); + } + + /** + * Test para correcta construccion de la instancia con scopes y uriApi interface + */ + public function testConstructCorrectInstanceWithCustomUri() + { + $service = new MicrosoftGraph( + $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); + } + + /** + * Test de obtenecion de url de autorizacion + */ + public function testGetAuthorizationEndpoint() + { + $service = new MicrosoftGraph( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertSame('https://login.microsoftonline.com/common/oauth2/authorize', $service->getAuthorizationEndpoint()->getAbsoluteUri()); + } + + /** + * Test para obtencion de url de endpoint del token + */ + public function testGetAccessTokenEndpoint() + { + $service = new MicrosoftGraph( + $this->getMock('\\OAuth\\Common\\Consumer\\CredentialsInterface'), + $this->getMock('\\OAuth\\Common\\Http\\Client\\ClientInterface'), + $this->getMock('\\OAuth\\Common\\Storage\\TokenStorageInterface') + ); + + $this->assertSame('https://login.microsoftonline.com/common/oauth2/token', $service->getAccessTokenEndpoint()->getAbsoluteUri()); + } + + +} \ No newline at end of file