diff --git a/.travis.yml b/.travis.yml index 1dfdaf38..9c5bea7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,10 +17,10 @@ deploy: - # test pypi provider: pypi distributions: sdist - server: https://testpypi.python.org/pypi + server: https://test.pypi.org/legacy/ user: "nugetaad" password: - secure: sEaIkr4od/4NJJq/3nvxjC/K/MF/KGBmN148dB4Sj/xMCA3DqqESMxhA8pB4YWfeZHHC6SOTqNHs6SEOxXg5JfY3eOoAke2GV0z/5VaDu/pYbjBZctxi2HC23hcQhDglW1nwfIw8Oglr9VLMbY8lls5AM69E4L/S8vB/hZvwl85WGsLIpod186GT00Ni0W+wnPVNJK+XP7xVb1BM1SRfioVDIsQ9W4qFWEYOLjJQvZByJvkb6Q971rwewZJFihYOZ3ynNeu+3lxifuCqag5KJgRmvaEynMuXx7NhO8kTic+kgcSG2YyDKBWVCrq5/8jJgAAj21n1QuyU3sdPtpVA/yoL41Lr2j1/lIsLbwzJdgaYcwn5QY8VccBNQyGctO/nSvDX6SbFnC1mYUb8J48AFg/EUsTmwqWqkXfBYwSRqgIqeMhh9vGYNh8Fxe9rRaAYTNMQa8ZZ6vBMoO71NhV4WC4tJEJ2KruWqrO9hUFHs4xV0EvAes9uCyzwk/pPE3oCZXt3BlKN5VMlaWzhA06g8oIIuovNnAKeqGXFKRwcVqVoPq+XWYfBy6OLP5b2rTBaYmTMNTkKEbTup66mP/DYbSN/j4g2wuW8koagg1p7a5I+lXrwBaDzLPBeJOxPCdzaU9rZJI8dsvFLDKqUY/wcbE8SoIMLwSwsudzMB5nS1Hw= + secure: Wm2jGolFLm/wrfSPklf9gYdWiTK7ycGr+Qa0voVmFEJkW69PRC5bCibJI3POK1DqTBmQn7gi5G0s117PoLlXvK9lqwMaDL6Yf/ro7YnMU9pBopoB/zWMxWYZeBJVugmTGKuTkbUiQBzL2h0EnaQvvyrEDiLGrYrYEgLUSuR5AVTlvYKk1XBAAhvh8hu8JjgQQugN2ne6ZR9aBjCap0fzdTs3vhad/OQx+iH8YR8UTl4ruszdoL95CDtFmKdIkwg0qgIB65MqC6XAQ2tvhyMDHXZMMafE0NQwUwm2d+sqinCfHLNkb5bVBS0M8syrYCS8xr6Ccnt0PM1+nNFm83bu1w+HaMwKWD2IaU26QH8H7djc7mO1XmRmMSxQ1EjR313YyF534+uiLBlJWB8DOfN4r3/lqg6e44CY0impiT7NnT47bUqaoglew5HB0FgrrtGDrDlLa7zf+RHyb2BhqeqlTR1s0nnzsmzQMdxaHXvCbzYPqg3PUdwLHGBks90tXhA0zUg/3XQfb7v17Lx1byRufvsWWYXUZwLI6H8CCvWtWFvJ3TSPPBR/5LjaICVtt2g3Uv2xrG3weCIO52G7WQ6pIpOyiRsYkUAIXLi2UNsv4LlpNxNObNgL7FNfrNR/tEs8+SdbAkaf2jrFfn+Sk7v4pdPd4og7YXWAE2R/ge9nsJ4= on: branch: master tags: false @@ -31,7 +31,7 @@ deploy: distributions: sdist user: "nugetaad" password: - secure: sEaIkr4od/4NJJq/3nvxjC/K/MF/KGBmN148dB4Sj/xMCA3DqqESMxhA8pB4YWfeZHHC6SOTqNHs6SEOxXg5JfY3eOoAke2GV0z/5VaDu/pYbjBZctxi2HC23hcQhDglW1nwfIw8Oglr9VLMbY8lls5AM69E4L/S8vB/hZvwl85WGsLIpod186GT00Ni0W+wnPVNJK+XP7xVb1BM1SRfioVDIsQ9W4qFWEYOLjJQvZByJvkb6Q971rwewZJFihYOZ3ynNeu+3lxifuCqag5KJgRmvaEynMuXx7NhO8kTic+kgcSG2YyDKBWVCrq5/8jJgAAj21n1QuyU3sdPtpVA/yoL41Lr2j1/lIsLbwzJdgaYcwn5QY8VccBNQyGctO/nSvDX6SbFnC1mYUb8J48AFg/EUsTmwqWqkXfBYwSRqgIqeMhh9vGYNh8Fxe9rRaAYTNMQa8ZZ6vBMoO71NhV4WC4tJEJ2KruWqrO9hUFHs4xV0EvAes9uCyzwk/pPE3oCZXt3BlKN5VMlaWzhA06g8oIIuovNnAKeqGXFKRwcVqVoPq+XWYfBy6OLP5b2rTBaYmTMNTkKEbTup66mP/DYbSN/j4g2wuW8koagg1p7a5I+lXrwBaDzLPBeJOxPCdzaU9rZJI8dsvFLDKqUY/wcbE8SoIMLwSwsudzMB5nS1Hw= + secure: Wm2jGolFLm/wrfSPklf9gYdWiTK7ycGr+Qa0voVmFEJkW69PRC5bCibJI3POK1DqTBmQn7gi5G0s117PoLlXvK9lqwMaDL6Yf/ro7YnMU9pBopoB/zWMxWYZeBJVugmTGKuTkbUiQBzL2h0EnaQvvyrEDiLGrYrYEgLUSuR5AVTlvYKk1XBAAhvh8hu8JjgQQugN2ne6ZR9aBjCap0fzdTs3vhad/OQx+iH8YR8UTl4ruszdoL95CDtFmKdIkwg0qgIB65MqC6XAQ2tvhyMDHXZMMafE0NQwUwm2d+sqinCfHLNkb5bVBS0M8syrYCS8xr6Ccnt0PM1+nNFm83bu1w+HaMwKWD2IaU26QH8H7djc7mO1XmRmMSxQ1EjR313YyF534+uiLBlJWB8DOfN4r3/lqg6e44CY0impiT7NnT47bUqaoglew5HB0FgrrtGDrDlLa7zf+RHyb2BhqeqlTR1s0nnzsmzQMdxaHXvCbzYPqg3PUdwLHGBks90tXhA0zUg/3XQfb7v17Lx1byRufvsWWYXUZwLI6H8CCvWtWFvJ3TSPPBR/5LjaICVtt2g3Uv2xrG3weCIO52G7WQ6pIpOyiRsYkUAIXLi2UNsv4LlpNxNObNgL7FNfrNR/tEs8+SdbAkaf2jrFfn+Sk7v4pdPd4og7YXWAE2R/ge9nsJ4= on: branch: master tags: true diff --git a/README.md b/README.md index a60795f6..b21a3d04 100644 --- a/README.md +++ b/README.md @@ -8,30 +8,24 @@ The ADAL for python library makes it easy for python applications to authenticat To support 'service principal' with certificate, ADAL depends on the 'cryptography' package. For smooth installation, some suggestions: -*For Windows and OSX +* For Windows and macOS Upgrade to the latest pip (8.1.2 as of June 2016) and just do `pip install adal`. -*For Linux +* For Linux + +Upgrade to the latest pip (8.1.2 as of June 2016). You'll need a C compiler, libffi + its development headers, and openssl + its development headers. Refer to [cryptography installation](https://cryptography.io/en/latest/installation/) -*To install from source: +* To install from source: +Upgrade to the latest pip (8.1.2 as of June 2016). Before run `python setup.py install`, to avoid dealing with compilation errors from cryptography, run `pip install cryptography` first to use statically-linked wheels. If you still like build from source, refer to [cryptography installation](https://cryptography.io/en/latest/installation/). For more context, starts with this [stackoverflow thread](http://stackoverflow.com/questions/22073516/failed-to-install-python-cryptography-package-with-pip-and-setup-py). -### About 'client_id' and 'resource' arguments -The convinient methods in 0.1.0 have been removed, and now your application should provide parameter values to `client_id` and `resource`. - -2 Reasons: - -* Each adal client should have a unique id representing an valid application registered in a tenant. The old methods borrowed the client-id of [azure-cli](https://github.com/Azure/azure-xplat-cli), which is never right. It is simple to register your application and get a client id. Many walkthroughs exist. You can follow [one of those] (http://www.bradygaster.com/post/using-windows-azure-active-directory-to-authenticate-the-management-libraries). Though that involves C# client, but the flow, and particularly the wizard snapshots are the same with adal-python. Do check out if you are new to AAD. - -* The old mmethod defaults the `resource` argument to 'https://management.core.windows.net/', now you can just supply this value explictly. Please note, there are lots of different azure resources you can acquire tokens through adal though, for example, the samples in the repository acquire for the 'graph' resource. Because it is not an appropriate assumption to be made at the library level, we removed the old defaults. - ### Acquire Token with Client Credentials 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/). @@ -89,13 +83,12 @@ code = context.acquire_user_code(RESOURCE, 'yourClientIdHere') print(code['message']) token = context.acquire_token_with_device_code(RESOURCE, code, 'yourClientIdHere') ``` - ### 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 auth_context.acquire_token_with_authorization_code( +return context.acquire_token_with_authorization_code( 'yourCodeFromQueryString', 'yourWebRedirectUri', RESOURCE, @@ -104,7 +97,11 @@ return auth_context.acquire_token_with_authorization_code( ``` ## Samples and Documentation -[We provide a full suite of sample applications and documentation on GitHub](https://github.com/AzureADSamples) to help you get started with learning the Azure Identity system. This includes tutorials for native clients such as Windows, Windows Phone, iOS, OSX, Android, and Linux. We also provide full walkthroughs for authentication flows such as OAuth2, OpenID Connect, Graph API, and other awesome features. +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. + +It is recommended to read the [Auth Scenarios](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-authentication-scenarios) doc, specifically the [Scenarios section](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-authentication-scenarios#application-types-and-scenarios). For some topics about registering/integrating an app, checkout [this doc](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-integrating-applications). And finally, we have a great topic on the Auth protocols you would be using and how they play with Azure AD in [this doc](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-openid-connect-code). + +While Python-specific samples will be added into the aforementioned documents as an on-going effort, you can always find [most relevant samples just inside this library repo](https://github.com/AzureAD/azure-activedirectory-library-for-python/tree/dev/sample). ## Community Help and Support @@ -132,3 +129,15 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope ### http tracing/proxy If need to bypass self-signed certificates, turn on the environment variable of `ADAL_PYTHON_SSL_NO_VERIFY` + + +## Note + +### Changes on 'client_id' and 'resource' arguments after 0.1.0 +The convinient methods in 0.1.0 have been removed, and now your application should provide parameter values to `client_id` and `resource`. + +2 Reasons: + +* Each adal client should have an Application ID representing an valid application registered in a tenant. The old methods borrowed the client-id of [azure-cli](https://github.com/Azure/azure-xplat-cli), which is never right. It is simple to register your application and get a client id. Many walkthroughs exist. You can follow [one of those](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-integrating-applications). Do check out if you are new to AAD. + +* The old method defaults the `resource` argument to 'https://management.core.windows.net/', now you can just supply this value explictly. Please note, there are lots of different azure resources you can acquire tokens through adal though, for example, the samples in the repository acquire for the 'graph' resource. Because it is not an appropriate assumption to be made at the library level, we removed the old defaults. diff --git a/adal/__init__.py b/adal/__init__.py index 1b32dab0..da939ffc 100644 --- a/adal/__init__.py +++ b/adal/__init__.py @@ -27,7 +27,7 @@ # pylint: disable=wrong-import-position -__version__ = '0.4.5' +__version__ = '0.4.6' import logging diff --git a/adal/cache_driver.py b/adal/cache_driver.py index fba053b7..9683dca4 100644 --- a/adal/cache_driver.py +++ b/adal/cache_driver.py @@ -164,11 +164,19 @@ def _refresh_entry_if_necessary(self, entry, is_resource_specific): now_plus_buffer = now + timedelta(minutes=Misc.CLOCK_BUFFER) if is_resource_specific and now_plus_buffer > expiry_date: - self._log.info('Cached token is expired. Refreshing: %s', expiry_date) - return self._refresh_expired_entry(entry) + if TokenResponseFields.REFRESH_TOKEN in entry: + self._log.info('Cached token is expired. Refreshing: %s', expiry_date) + return self._refresh_expired_entry(entry) + else: + self.remove(entry) + return None elif not is_resource_specific and entry.get(TokenResponseFields.IS_MRRT): - self._log.info('Acquiring new access token from MRRT token.') - return self._acquire_new_token_from_mrrt(entry) + if TokenResponseFields.REFRESH_TOKEN in entry: + self._log.info('Acquiring new access token from MRRT token.') + return self._acquire_new_token_from_mrrt(entry) + else: + self.remove(entry) + return None else: return entry diff --git a/adal/constants.py b/adal/constants.py index 1d532aef..fb8c73c3 100644 --- a/adal/constants.py +++ b/adal/constants.py @@ -206,7 +206,14 @@ class HttpError(object): class AADConstants(object): WORLD_WIDE_AUTHORITY = 'login.windows.net' - WELL_KNOWN_AUTHORITY_HOSTS = ['login.windows.net', 'login.microsoftonline.com', 'login.chinacloudapi.cn', 'login-us.microsoftonline.com', 'login.microsoftonline.de'] + WELL_KNOWN_AUTHORITY_HOSTS = [ + 'login.windows.net', + 'login.microsoftonline.com', + 'login.chinacloudapi.cn', + 'login-us.microsoftonline.com', + 'login.microsoftonline.us', + 'login.microsoftonline.de', + ] INSTANCE_DISCOVERY_ENDPOINT_TEMPLATE = 'https://{authorize_host}/common/discovery/instance?authorization_endpoint={authorize_endpoint}&api-version=1.0' # pylint: disable=invalid-name AUTHORIZE_ENDPOINT_PATH = '/oauth2/authorize' TOKEN_ENDPOINT_PATH = '/oauth2/token' diff --git a/adal/oauth2_client.py b/adal/oauth2_client.py index dd26c4ab..979a292e 100644 --- a/adal/oauth2_client.py +++ b/adal/oauth2_client.py @@ -191,10 +191,10 @@ def _validate_token_response(self, body): wire_response[OAuth2.ResponseParameters.CREATED_ON] = str(temp_date) if not wire_response.get(OAuth2.ResponseParameters.TOKEN_TYPE): - raise AdalError('wire_response is missing token_type') + raise AdalError('wire_response is missing token_type', wire_response) if not wire_response.get(OAuth2.ResponseParameters.ACCESS_TOKEN): - raise AdalError('wire_response is missing access_token') + raise AdalError('wire_response is missing access_token', wire_response) token_response = map_fields(wire_response, TOKEN_RESPONSE_MAP) diff --git a/sample/certificate_credentials_sample.py b/sample/certificate_credentials_sample.py index 8723616f..aaadb4f2 100644 --- a/sample/certificate_credentials_sample.py +++ b/sample/certificate_credentials_sample.py @@ -6,11 +6,11 @@ def turn_on_logging(): logging.basicConfig(level=logging.DEBUG) - #or, + #or, #handler = logging.StreamHandler() #adal.set_logging_options({ # 'level': 'DEBUG', - # 'handler': handler + # 'handler': handler #}) #handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT)) @@ -20,19 +20,20 @@ def get_private_key(filename): return private_pem # -# You can provide account information by using a JSON file. Either -# through a command line argument, 'python sample.js parameters.json', or +# You can provide account information by using a JSON file. Either +# through a command line argument, 'python sample.py parameters.json', or # specifying in an environment variable of ADAL_SAMPLE_PARAMETERS_FILE. # privateKeyFile must contain a PEM encoded cert with private key. # thumbprint must be the thumbprint of the privateKeyFile. # { +# "resource": "your_resource", # "tenant" : "naturalcauses.onmicrosoft.com", # "authorityHostUrl" : "https://login.microsoftonline.com", # "clientId" : "d6835713-b745-48d1-bb62-7a8248477d35", # "thumbprint" : 'C15DEA8656ADDF67BE8031D85EBDDC5AD6C436E1', # "privateKeyFile" : 'ncwebCTKey.pem' # } -parameters_file = (sys.argv[1] if len(sys.argv) == 2 else +parameters_file = (sys.argv[1] if len(sys.argv) == 2 else os.environ.get('ADAL_SAMPLE_PARAMETERS_FILE')) sample_parameters = {} if parameters_file: @@ -43,20 +44,21 @@ def get_private_key(filename): raise ValueError('Please provide parameter file with account information.') -authority_url = (sample_parameters['authorityHostUrl'] + '/' + +authority_url = (sample_parameters['authorityHostUrl'] + '/' + sample_parameters['tenant']) -RESOURCE = '00000002-0000-0000-c000-000000000000' +GRAPH_RESOURCE = '00000002-0000-0000-c000-000000000000' +RESOURCE = sample_parameters.get('resource', GRAPH_RESOURCE) #uncomment for verbose logging turn_on_logging() -context = adal.AuthenticationContext(authority_url) +context = adal.AuthenticationContext(authority_url, api_version=None) key = get_private_key(sample_parameters['privateKeyFile']) token = context.acquire_token_with_client_certificate( - RESOURCE, - sample_parameters['clientId'], - key, + RESOURCE, + sample_parameters['clientId'], + key, sample_parameters['thumbprint']) print('Here is the token:') diff --git a/sample/client_credentials_sample.py b/sample/client_credentials_sample.py index 787a1b21..57c6aa35 100644 --- a/sample/client_credentials_sample.py +++ b/sample/client_credentials_sample.py @@ -6,25 +6,26 @@ def turn_on_logging(): logging.basicConfig(level=logging.DEBUG) - #or, + #or, #handler = logging.StreamHandler() #adal.set_logging_options({ # 'level': 'DEBUG', - # 'handler': handler + # 'handler': handler #}) #handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT)) # You can provide account information by using a JSON file. Either -# through a command line argument, 'python sample.js parameters.json', or +# through a command line argument, 'python sample.py parameters.json', or # specifying in an environment variable of ADAL_SAMPLE_PARAMETERS_FILE. # { +# "resource": "your_resource", # "tenant" : "rrandallaad1.onmicrosoft.com", # "authorityHostUrl" : "https://login.microsoftonline.com", # "clientId" : "624ac9bd-4c1c-4687-aec8-b56a8991cfb3", # "clientSecret" : "verySecret="" # } -parameters_file = (sys.argv[1] if len(sys.argv) == 2 else +parameters_file = (sys.argv[1] if len(sys.argv) == 2 else os.environ.get('ADAL_SAMPLE_PARAMETERS_FILE')) if parameters_file: @@ -33,19 +34,22 @@ def turn_on_logging(): sample_parameters = json.loads(parameters) else: raise ValueError('Please provide parameter file with account information.') - -authority_url = (sample_parameters['authorityHostUrl'] + '/' + + +authority_url = (sample_parameters['authorityHostUrl'] + '/' + sample_parameters['tenant']) -RESOURCE = '00000002-0000-0000-c000-000000000000' +GRAPH_RESOURCE = '00000002-0000-0000-c000-000000000000' +RESOURCE = sample_parameters.get('resource', GRAPH_RESOURCE) #uncomment for verbose log #turn_on_logging() -context = adal.AuthenticationContext(authority_url) +context = adal.AuthenticationContext( + authority_url, validate_authority=sample_parameters['tenant'] != 'adfs', + api_version=None) token = context.acquire_token_with_client_credentials( RESOURCE, - sample_parameters['clientId'], + sample_parameters['clientId'], sample_parameters['clientSecret']) print('Here is the token:') diff --git a/sample/device_code_sample.py b/sample/device_code_sample.py index 460c5bc9..21728d94 100644 --- a/sample/device_code_sample.py +++ b/sample/device_code_sample.py @@ -6,26 +6,27 @@ def turn_on_logging(): logging.basicConfig(level=logging.DEBUG) - #or, + #or, #handler = logging.StreamHandler() #adal.set_logging_options({ # 'level': 'DEBUG', - # 'handler': handler + # 'handler': handler #}) #handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT)) # You can provide account information by using a JSON file # with the same parameters as the sampleParameters variable below. Either -# through a command line argument, 'python sample.js parameters.json', or +# through a command line argument, 'python sample.py parameters.json', or # specifying in an environment variable of ADAL_SAMPLE_PARAMETERS_FILE. # { +# "resource": "your_resource", # "tenant" : "rrandallaad1.onmicrosoft.com", # "authorityHostUrl" : "https://login.microsoftonline.com", # "clientid" : "", # "anothertenant" : "bar.onmicrosoft.com" # } -parameters_file = (sys.argv[1] if len(sys.argv) == 2 else +parameters_file = (sys.argv[1] if len(sys.argv) == 2 else os.environ.get('ADAL_SAMPLE_PARAMETERS_FILE')) if parameters_file: @@ -39,12 +40,13 @@ def turn_on_logging(): authority_host_url = sample_parameters['authorityHostUrl'] authority_url = authority_host_url + '/' + sample_parameters['tenant'] clientid = sample_parameters['clientid'] -RESOURCE = '00000002-0000-0000-c000-000000000000' +GRAPH_RESOURCE = '00000002-0000-0000-c000-000000000000' +RESOURCE = sample_parameters.get('resource', GRAPH_RESOURCE) -#uncomment for verbose logging +#uncomment for verbose logging #turn_on_logging() -context = adal.AuthenticationContext(authority_url) +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) @@ -56,7 +58,7 @@ def turn_on_logging(): another_tenant = sample_parameters.get('anothertenant') if another_tenant: authority_url = authority_host_url + '/' + another_tenant - #reuse existing cache which has the tokens acquired early on + #reuse existing cache which has the tokens acquired early on existing_cache = context.cache context = adal.AuthenticationContext(authority_url, cache=existing_cache) token = context.acquire_token(RESOURCE, token['userId'], clientid) diff --git a/sample/refresh_token_sample.py b/sample/refresh_token_sample.py index e97ba11c..d25b2f56 100644 --- a/sample/refresh_token_sample.py +++ b/sample/refresh_token_sample.py @@ -6,18 +6,19 @@ def turn_on_logging(): logging.basicConfig(level=logging.DEBUG) - #or, + #or, #handler = logging.StreamHandler() #adal.set_logging_options({ # 'level': 'DEBUG', - # 'handler': handler + # 'handler': handler #}) #handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT)) # You can override the account information by using a JSON file. Either -# through a command line argument, 'python sample.js parameters.json', or +# through a command line argument, 'python sample.py parameters.json', or # specifying in an environment variable of ADAL_SAMPLE_PARAMETERS_FILE. # { +# "resource": "your_resource", # "tenant" : "rrandallaad1.onmicrosoft.com", # "authorityHostUrl" : "https://login.microsoftonline.com", # "clientid" : "624ac9bd-4c1c-4687-aec8-b56a8991cfb3", @@ -25,7 +26,7 @@ def turn_on_logging(): # "password" : "verySecurePassword" # } -parameters_file = (sys.argv[1] if len(sys.argv) == 2 else +parameters_file = (sys.argv[1] if len(sys.argv) == 2 else os.environ.get('ADAL_SAMPLE_PARAMETERS_FILE')) if parameters_file: @@ -35,17 +36,20 @@ def turn_on_logging(): else: raise ValueError('Please provide parameter file with account information.') -authority_url = (sample_parameters['authorityHostUrl'] + '/' + +authority_url = (sample_parameters['authorityHostUrl'] + '/' + sample_parameters['tenant']) -RESOURCE = '00000002-0000-0000-c000-000000000000' +GRAPH_RESOURCE = '00000002-0000-0000-c000-000000000000' +RESOURCE = sample_parameters.get('resource', GRAPH_RESOURCE) #uncomment for verbose log #turn_on_logging() -context = adal.AuthenticationContext(authority_url) +context = adal.AuthenticationContext( + authority_url, validate_authority=sample_parameters['tenant'] != 'adfs', + api_version=None) token = context.acquire_token_with_username_password( - RESOURCE, + RESOURCE, sample_parameters['username'], sample_parameters['password'], sample_parameters['clientid']) diff --git a/sample/website_sample.py b/sample/website_sample.py index f333d6fc..162610dc 100644 --- a/sample/website_sample.py +++ b/sample/website_sample.py @@ -1,4 +1,4 @@ -try: +try: from http import server as httpserver from http import cookies as Cookie except ImportError: @@ -24,16 +24,17 @@ from adal import AuthenticationContext # You can provide account information by using a JSON file. Either -# through a command line argument, 'python sample.js parameters.json', or +# through a command line argument, 'python sample.py parameters.json', or # specifying in an environment variable of ADAL_SAMPLE_PARAMETERS_FILE. # { +# "resource": "your_resource", # "tenant" : "rrandallaad1.onmicrosoft.com", # "authorityHostUrl" : "https://login.microsoftonline.com", # "clientId" : "624ac9bd-4c1c-4687-aec8-b56a8991cfb3", # "clientSecret" : "verySecret="" # } -parameters_file = (sys.argv[1] if len(sys.argv) == 2 else +parameters_file = (sys.argv[1] if len(sys.argv) == 2 else os.environ.get('ADAL_SAMPLE_PARAMETERS_FILE')) if parameters_file: @@ -44,13 +45,14 @@ raise ValueError('Please provide parameter file with account information.') PORT = 8088 -TEMPLATE_AUTHZ_URL = ('https://login.windows.net/{}/oauth2/authorize?'+ +TEMPLATE_AUTHZ_URL = ('https://login.windows.net/{}/oauth2/authorize?'+ 'response_type=code&client_id={}&redirect_uri={}&'+ 'state={}&resource={}') -RESOURCE = '00000002-0000-0000-c000-000000000000' #Graph Resource +GRAPH_RESOURCE = '00000002-0000-0000-c000-000000000000' +RESOURCE = sample_parameters.get('resource', GRAPH_RESOURCE) REDIRECT_URI = 'http://localhost:{}/getAToken'.format(PORT) -authority_url = (sample_parameters['authorityHostUrl'] + '/' + +authority_url = (sample_parameters['authorityHostUrl'] + '/' + sample_parameters['tenant']) class OAuth2RequestHandler(httpserver.SimpleHTTPRequestHandler): @@ -61,16 +63,16 @@ def do_GET(self): self.send_header('Location', login_url) self.end_headers() elif self.path == '/login': - auth_state = (''.join(random.SystemRandom() + auth_state = (''.join(random.SystemRandom() .choice(string.ascii_uppercase + string.digits) for _ in range(48))) cookie = Cookie.SimpleCookie() cookie['auth_state'] = auth_state authorization_url = TEMPLATE_AUTHZ_URL.format( - sample_parameters['tenant'], - sample_parameters['clientId'], - REDIRECT_URI, - auth_state, + sample_parameters['tenant'], + sample_parameters['clientId'], + REDIRECT_URI, + auth_state, RESOURCE) self.send_response(307) self.send_header('Set-Cookie', cookie.output(header='')) @@ -82,19 +84,19 @@ def do_GET(self): token_response = self._acquire_token() message = 'response: ' + json.dumps(token_response) #Later, if the access token is expired it can be refreshed. - auth_context = AuthenticationContext(authority_url) + auth_context = AuthenticationContext(authority_url, api_version=None) token_response = auth_context.acquire_token_with_refresh_token( token_response['refreshToken'], sample_parameters['clientId'], RESOURCE, sample_parameters['clientSecret']) - message = (message + '*** And here is the refresh response:' + + message = (message + '*** And here is the refresh response:' + json.dumps(token_response)) except ValueError as exp: message = str(exp) is_ok = False self._send_response(message, is_ok) - + def _acquire_token(self): parsed = urlparse(self.path) code = parse_qs(parsed.query)['code'][0] @@ -102,12 +104,12 @@ def _acquire_token(self): cookie = Cookie.SimpleCookie(self.headers["Cookie"]) if state != cookie['auth_state'].value: raise ValueError('state does not match') - auth_context = AuthenticationContext(authority_url) + auth_context = AuthenticationContext(authority_url, api_version=None) return auth_context.acquire_token_with_authorization_code( - code, - REDIRECT_URI, - RESOURCE, - sample_parameters['clientId'], + code, + REDIRECT_URI, + RESOURCE, + sample_parameters['clientId'], sample_parameters['clientSecret']) def _send_response(self, message, is_ok=True): diff --git a/setup.py b/setup.py index 5a7b3eb7..a8800300 100644 --- a/setup.py +++ b/setup.py @@ -27,12 +27,13 @@ #------------------------------------------------------------------------------ from setuptools import setup -import re +import re, io # setup.py shall not import adal __version__ = re.search( r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', # It excludes inline comment too - open('adal/__init__.py').read()).group(1) + io.open('adal/__init__.py', encoding='utf_8_sig').read() + ).group(1) # To build: # python setup.py sdist diff --git a/tests/test_cache_driver.py b/tests/test_cache_driver.py new file mode 100644 index 00000000..b3c4e077 --- /dev/null +++ b/tests/test_cache_driver.py @@ -0,0 +1,58 @@ +#------------------------------------------------------------------------------ +# +# Copyright (c) Microsoft Corporation. +# All rights reserved. +# +# This code is licensed under the MIT License. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files(the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions : +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +#------------------------------------------------------------------------------ + +import unittest +try: + from unittest import mock +except ImportError: + import mock + +from adal.log import create_log_context +from adal.cache_driver import CacheDriver + + +class TestCacheDriver(unittest.TestCase): + def test_rt_less_item_wont_cause_exception(self): # Github issue #82 + rt_less_entry_came_from_previous_client_credentials_grant = { + "expiresIn": 3600, + "_authority": "https://login.microsoftonline.com/foo", + "resource": "spn:00000002-0000-0000-c000-000000000000", + "tokenType": "Bearer", + "expiresOn": "1999-05-22 16:31:46.202000", + "isMRRT": True, + "_clientId": "client_id", + "accessToken": "this is an AT", + } + refresh_function = mock.MagicMock(return_value={}) + cache_driver = CacheDriver( + {"log_context": create_log_context()}, "authority", "resource", + "client_id", mock.MagicMock(), refresh_function) + entry = cache_driver._refresh_entry_if_necessary( + rt_less_entry_came_from_previous_client_credentials_grant, False) + refresh_function.assert_not_called() # Otherwise it will cause an exception + self.assertIsNone(entry) +