diff --git a/.travis.yml b/.travis.yml index 9c5bea7c..6003951e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ script: deploy: - # test pypi provider: pypi - distributions: sdist + distributions: "sdist bdist_wheel" server: https://test.pypi.org/legacy/ user: "nugetaad" password: @@ -28,7 +28,7 @@ deploy: - # production pypi provider: pypi - distributions: sdist + distributions: "sdist bdist_wheel" user: "nugetaad" password: secure: Wm2jGolFLm/wrfSPklf9gYdWiTK7ycGr+Qa0voVmFEJkW69PRC5bCibJI3POK1DqTBmQn7gi5G0s117PoLlXvK9lqwMaDL6Yf/ro7YnMU9pBopoB/zWMxWYZeBJVugmTGKuTkbUiQBzL2h0EnaQvvyrEDiLGrYrYEgLUSuR5AVTlvYKk1XBAAhvh8hu8JjgQQugN2ne6ZR9aBjCap0fzdTs3vhad/OQx+iH8YR8UTl4ruszdoL95CDtFmKdIkwg0qgIB65MqC6XAQ2tvhyMDHXZMMafE0NQwUwm2d+sqinCfHLNkb5bVBS0M8syrYCS8xr6Ccnt0PM1+nNFm83bu1w+HaMwKWD2IaU26QH8H7djc7mO1XmRmMSxQ1EjR313YyF534+uiLBlJWB8DOfN4r3/lqg6e44CY0impiT7NnT47bUqaoglew5HB0FgrrtGDrDlLa7zf+RHyb2BhqeqlTR1s0nnzsmzQMdxaHXvCbzYPqg3PUdwLHGBks90tXhA0zUg/3XQfb7v17Lx1byRufvsWWYXUZwLI6H8CCvWtWFvJ3TSPPBR/5LjaICVtt2g3Uv2xrG3weCIO52G7WQ6pIpOyiRsYkUAIXLi2UNsv4LlpNxNObNgL7FNfrNR/tEs8+SdbAkaf2jrFfn+Sk7v4pdPd4og7YXWAE2R/ge9nsJ4= diff --git a/README.md b/README.md index b21a3d04..3d45d538 100644 --- a/README.md +++ b/README.md @@ -30,71 +30,21 @@ For more context, starts with this [stackoverflow thread](http://stackoverflow.c In order to use this token acquisition method, you need to configure a service principal. Please follow [this walkthrough](https://azure.microsoft.com/en-us/documentation/articles/resource-group-create-service-principal-portal/). -See the [sample](./sample/client_credentials_sample.py). -```python -import adal - -context = adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL') -RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource -token = context.acquire_token_with_client_credentials( - RESOURCE, - "http://PythonSDK", - "Key-Configured-In-Portal") -``` +Find the `Main logic` part in the [sample](sample/client_credentials_sample.py#L46-L55). ### Acquire Token with client certificate -A service principal is also required. See the [sample](./sample/certificate_credentials_sample.py). -```python -import adal -context = adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL') -RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource -token = context.acquire_token_with_client_certificate( - RESOURCE, - "http://PythonSDK", - 'yourPrivateKeyFileContent', - 'thumbprintOfPrivateKey') -``` +A service principal is also required. +Find the `Main logic` part in the [sample](sample/certificate_credentials_sample.py#L55-L64). ### Acquire Token with Refresh Token -See the [sample](./sample/refresh_token_sample.py). -```python -import adal -context = adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL') -RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource -token = context.acquire_token_with_username_password( - RESOURCE, - 'yourName', - 'yourPassword', - 'yourClientIdHere') - -refresh_token = token['refreshToken'] -token = context.acquire_token_with_refresh_token( - refresh_token, - 'yourClientIdHere', - RESOURCE) -``` +Find the `Main logic` part in the [sample](sample/refresh_token_sample.py#L47-L69). ### Acquire Token with device code -See the [sample](./sample/device_code_sample.py). -```python -context = adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL') -RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource -code = context.acquire_user_code(RESOURCE, 'yourClientIdHere') -print(code['message']) -token = context.acquire_token_with_device_code(RESOURCE, code, 'yourClientIdHere') -``` +Find the `Main logic` part in the [sample](sample/device_code_sample.py#L49-L54). + ### Acquire Token with authorization code -See the [sample](./sample/website_sample.py) for a complete bare bones web site that makes use of the code below. -```python -context = adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL') -RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource -return context.acquire_token_with_authorization_code( - 'yourCodeFromQueryString', - 'yourWebRedirectUri', - RESOURCE, - 'yourClientId', - 'yourClientSecret') -``` +Find the `Main logic` part in the [sample](sample/website_sample.py#L107-L115) for a complete bare bones web site that makes use of the code below. + ## Samples and Documentation We provide a full suite of [sample applications on GitHub](https://github.com/azure-samples?utf8=%E2%9C%93&q=active-directory&type=&language=) and an [Azure AD developer landing page](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-developers-guide) to help you get started with learning the Azure Identity system. This includes tutorials for native clients and web applications. We also provide full walkthroughs for authentication flows such as OAuth2, OpenID Connect and for calling APIs such as the Graph API. diff --git a/adal/__init__.py b/adal/__init__.py index da939ffc..f73f038a 100644 --- a/adal/__init__.py +++ b/adal/__init__.py @@ -27,7 +27,7 @@ # pylint: disable=wrong-import-position -__version__ = '0.4.6' +__version__ = '0.4.7' import logging diff --git a/adal/cache_driver.py b/adal/cache_driver.py index 9683dca4..788cbe99 100644 --- a/adal/cache_driver.py +++ b/adal/cache_driver.py @@ -132,6 +132,12 @@ def _create_entry_from_refresh(self, entry, refresh_response): new_entry = copy.deepcopy(entry) new_entry.update(refresh_response) + # It is possible the response payload has no 'resource' field, like in ADFS, so we manually + # fill it here. Note, 'resource' is part of the token cache key, so we have to set it to avoid + # corrupting the cache. + if 'resource' not in refresh_response: + new_entry['resource'] = self._resource + if entry[TokenResponseFields.IS_MRRT] and self._authority != entry[TokenResponseFields._AUTHORITY]: new_entry[TokenResponseFields._AUTHORITY] = self._authority diff --git a/sample/certificate_credentials_sample.py b/sample/certificate_credentials_sample.py index aaadb4f2..5578f0d0 100644 --- a/sample/certificate_credentials_sample.py +++ b/sample/certificate_credentials_sample.py @@ -52,6 +52,7 @@ def get_private_key(filename): #uncomment for verbose logging turn_on_logging() +### Main logic begins context = adal.AuthenticationContext(authority_url, api_version=None) key = get_private_key(sample_parameters['privateKeyFile']) @@ -60,6 +61,7 @@ def get_private_key(filename): sample_parameters['clientId'], key, sample_parameters['thumbprint']) +### Main logic ends print('Here is the token:') print(json.dumps(token, indent=2)) diff --git a/sample/client_credentials_sample.py b/sample/client_credentials_sample.py index 57c6aa35..37cffd6e 100644 --- a/sample/client_credentials_sample.py +++ b/sample/client_credentials_sample.py @@ -43,6 +43,7 @@ def turn_on_logging(): #uncomment for verbose log #turn_on_logging() +### Main logic begins context = adal.AuthenticationContext( authority_url, validate_authority=sample_parameters['tenant'] != 'adfs', api_version=None) @@ -51,6 +52,7 @@ def turn_on_logging(): RESOURCE, sample_parameters['clientId'], sample_parameters['clientSecret']) +### Main logic ends print('Here is the token:') print(json.dumps(token, indent=2)) diff --git a/sample/device_code_sample.py b/sample/device_code_sample.py index 21728d94..3d9145b1 100644 --- a/sample/device_code_sample.py +++ b/sample/device_code_sample.py @@ -46,10 +46,12 @@ def turn_on_logging(): #uncomment for verbose logging #turn_on_logging() +### Main logic begins context = adal.AuthenticationContext(authority_url, api_version=None) code = context.acquire_user_code(RESOURCE, clientid) print(code['message']) token = context.acquire_token_with_device_code(RESOURCE, code, clientid) +### Main logic ends print('Here is the token from "{}":'.format(authority_url)) print(json.dumps(token, indent=2)) diff --git a/sample/refresh_token_sample.py b/sample/refresh_token_sample.py index d25b2f56..0b5bfafb 100644 --- a/sample/refresh_token_sample.py +++ b/sample/refresh_token_sample.py @@ -44,6 +44,7 @@ def turn_on_logging(): #uncomment for verbose log #turn_on_logging() +### Main logic begins context = adal.AuthenticationContext( authority_url, validate_authority=sample_parameters['tenant'] != 'adfs', api_version=None) @@ -61,7 +62,11 @@ def turn_on_logging(): token = context.acquire_token_with_refresh_token( refresh_token, sample_parameters['clientid'], - RESOURCE) + RESOURCE, + # client_secret="your_secret" # This is needed when using Confidential Client, + # otherwise you will encounter an invalid_client error. + ) +### Main logic ends print('Here is the token acquired from the refreshing token') print(json.dumps(token, indent=2)) diff --git a/sample/website_sample.py b/sample/website_sample.py index 162610dc..fd57d66a 100644 --- a/sample/website_sample.py +++ b/sample/website_sample.py @@ -104,6 +104,7 @@ def _acquire_token(self): cookie = Cookie.SimpleCookie(self.headers["Cookie"]) if state != cookie['auth_state'].value: raise ValueError('state does not match') + ### Main logic begins auth_context = AuthenticationContext(authority_url, api_version=None) return auth_context.acquire_token_with_authorization_code( code, @@ -111,6 +112,7 @@ def _acquire_token(self): RESOURCE, sample_parameters['clientId'], sample_parameters['clientSecret']) + ### Main logic ends def _send_response(self, message, is_ok=True): self.send_response(200 if is_ok else 400) diff --git a/tests/test_refresh_token.py b/tests/test_refresh_token.py index 3e19e069..4c958f62 100644 --- a/tests/test_refresh_token.py +++ b/tests/test_refresh_token.py @@ -41,31 +41,61 @@ import mock import adal -from adal.authentication_context import AuthenticationContext +from adal.authentication_context import AuthenticationContext, TokenCache from tests import util from tests.util import parameters as cp class TestRefreshToken(unittest.TestCase): - def setUp(self): - self.response_options = { 'refreshedRefresh' : True } - self.response = util.create_response(self.response_options) - self.wire_response = self.response['wireResponse'] @httpretty.activate def test_happy_path_with_resource_client_secret(self): - tokenRequest = util.setup_expected_refresh_token_request_response(200, self.wire_response, self.response['authority'], self.response['resource'], cp['clientSecret']) + response_options = { 'refreshedRefresh' : True } + response = util.create_response(response_options) + wire_response = response['wireResponse'] + tokenRequest = util.setup_expected_refresh_token_request_response(200, wire_response, response['authority'], response['resource'], cp['clientSecret']) context = adal.AuthenticationContext(cp['authorityTenant']) def side_effect (tokenfunc): - return self.response['decodedResponse'] + return response['decodedResponse'] context._acquire_token = mock.MagicMock(side_effect=side_effect) token_response = context.acquire_token_with_refresh_token(cp['refreshToken'], cp['clientId'], cp['clientSecret'], cp['resource']) self.assertTrue( - util.is_match_token_response(self.response['decodedResponse'], token_response), + util.is_match_token_response(response['decodedResponse'], token_response), 'The response did not match what was expected: ' + str(token_response) ) + @httpretty.activate + def test_happy_path_with_resource_adfs(self): + # arrange + # set up token refresh result + wire_response = util.create_response({ + 'refreshedRefresh' : True, + 'mrrt': False + })['wireResponse'] + new_resource = 'https://graph.local.azurestack.external/' + tokenRequest = util.setup_expected_refresh_token_request_response(200, wire_response, cp['authority'], new_resource) + + # set up an existing token to be used for refreshing + existing_token = util.create_response({ + 'refreshedRefresh': True, + 'mrrt': True + })['decodedResponse'] + existing_token['_clientId'] = existing_token.get('_clientId') or cp['clientId'] + existing_token['isMRRT'] = existing_token.get('isMRRT') or True + existing_token['_authority'] = existing_token.get('_authority') or cp['authorizeUrl'] + token_cache = TokenCache(json.dumps([existing_token])) + + # act + user_id = existing_token['userId'] + context = adal.AuthenticationContext(cp['authorityTenant'], cache=token_cache) + token_response = context.acquire_token(new_resource, user_id, cp['clientId']) + + # assert + tokens = [value for key, value in token_cache.read_items()] + self.assertEqual(2, len(tokens)) + self.assertEqual({cp['resource'], new_resource}, set([x['resource'] for x in tokens])) + if __name__ == '__main__': unittest.main()