pax_global_header00006660000000000000000000000064140326346510014516gustar00rootroot0000000000000052 comment=b9890f96b17da99999d8cd752b6a19a36b02304e azure-activedirectory-library-for-python-1.2.7/000077500000000000000000000000001403263465100216165ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/.github/000077500000000000000000000000001403263465100231565ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/.github/ISSUE_TEMPLATE/000077500000000000000000000000001403263465100253415ustar00rootroot00000000000000a-template-reminding-adal-s-status.md000066400000000000000000000016121403263465100342670ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/.github/ISSUE_TEMPLATE--- name: A template reminding ADAL's status about: So that people are guided to use MSAL Python instead. title: '' labels: '' assignees: '' --- This library, ADAL for Python, will no longer receive new feature improvements. Instead, use the new library [MSAL for Python](https://github.com/AzureAD/microsoft-authentication-library-for-python). * If you are starting a new project, you can get started with the MSAL Python docs for details about the scenarios, usage, and relevant concepts. * If your application is using the previous ADAL Python library, you can follow this migration guide to update to MSAL Python. * Existing applications relying on ADAL Python will continue to work. --- If you encounter a bug, please reproduce it using our off-the-shelf [samples](https://github.com/AzureAD/azure-activedirectory-library-for-python/tree/1.2.4/sample), so that we can follow your steps. azure-activedirectory-library-for-python-1.2.7/.gitignore000066400000000000000000000010751403263465100236110ustar00rootroot00000000000000# Python cache __pycache__/ *.pyc # PTVS analysis .ptvs/ # Build results /bin/ /obj/ /dist/ /MANIFEST # Result of running python setup.py install/pip install -e /build/ /adal.egg-info/ # Test results /TestResults/ # User-specific files *.suo *.user *.sln.docstates /tests/config.py # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Mac desktop service store files .DS_Store .idea src/build *.iml /doc/_build # Virtual Environments /env* # Visual Studio Files /.vs/* /tests/.vs/*azure-activedirectory-library-for-python-1.2.7/.travis.yml000066400000000000000000000044451403263465100237360ustar00rootroot00000000000000sudo: false language: python python: - "2.7" - "3.5" - "3.6" install: - pip install -r requirements.txt script: - # PyLint does not yet support Python 3.6 https://github.com/PyCQA/pylint/issues/1241 if [ "$TRAVIS_PYTHON_VERSION" != "3.6" ]; then pylint adal; fi - python -m unittest discover -s tests deploy: - # test pypi provider: pypi distributions: "sdist bdist_wheel" server: https://test.pypi.org/legacy/ user: "nugetaad" password: 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 # At Apr 2021, the get-pip.py used by Travis CI requires Python 3.6+ condition: $TRAVIS_PYTHON_VERSION = "3.6" - # production pypi provider: pypi 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= on: branch: master tags: true # At Apr 2021, the get-pip.py used by Travis CI requires Python 3.6+ condition: $TRAVIS_PYTHON_VERSION = "3.6" azure-activedirectory-library-for-python-1.2.7/LICENSE000066400000000000000000000022001403263465100226150ustar00rootroot00000000000000The MIT License (MIT) 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.azure-activedirectory-library-for-python-1.2.7/README.md000066400000000000000000000130151403263465100230750ustar00rootroot00000000000000--- This library, ADAL for Python, will no longer receive new feature improvements. Instead, use the new library [MSAL for Python](https://github.com/AzureAD/microsoft-authentication-library-for-python). * If you are starting a new project, you can get started with the [MSAL Python docs](https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki) for details about the scenarios, usage, and relevant concepts. * If your application is using the previous ADAL Python library, you can follow this [migration guide](https://docs.microsoft.com/en-us/azure/active-directory/develop/migrate-python-adal-msal) to update to MSAL Python. * Existing applications relying on ADAL Python will continue to work. --- # Microsoft Azure Active Directory Authentication Library (ADAL) for Python `master` branch | `dev` branch | Reference Docs --------------------|-----------------|--------------- [![Build Status](https://travis-ci.org/AzureAD/azure-activedirectory-library-for-python.svg?branch=master)](https://travis-ci.org/AzureAD/azure-activedirectory-library-for-python) | [![Build Status](https://travis-ci.org/AzureAD/azure-activedirectory-library-for-python.svg?branch=dev)](https://travis-ci.org/AzureAD/azure-activedirectory-library-for-python) | [![Documentation Status](https://readthedocs.org/projects/adal-python/badge/?version=latest)](https://adal-python.readthedocs.io/en/latest/?badge=latest) |[Getting Started](https://github.com/AzureAD/azure-activedirectory-library-for-python/wiki)| [Docs](https://aka.ms/aaddev)| [Python Samples](https://github.com/Azure-Samples?q=active-directory&language=python)| [Support](README.md#community-help-and-support) | [Feedback](https://forms.office.com/r/wX0UuEF8kX) | --- | --- | --- | --- | --- | The ADAL for Python library enables python applications to authenticate with Azure AD and get tokens to access Azure AD protected web resources. You can learn in detail about ADAL Python functionality and usage documented in the [Wiki](https://github.com/AzureAD/azure-activedirectory-library-for-python/wiki). ## Installation and Usage You can find the steps to install and basic usage of the library under [ADAL Basics](https://github.com/AzureAD/azure-activedirectory-library-for-python/wiki/ADAL-basics) page in the Wiki. ## Samples and Documentation We provide a full suite of [Python sample applications on GitHub](https://github.com/Azure-Samples?q=active-directory&language=python) to help you get started with learning the Azure Identity system. This will include 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. There are also some [lightweight samples existing inside this repo](https://github.com/AzureAD/azure-activedirectory-library-for-python/tree/dev/sample). You can find the relevant samples by scenarios listed in this [wiki page for acquiring tokens using ADAL Python](https://github.com/AzureAD/azure-activedirectory-library-for-python/wiki/Acquire-tokens#adal-python-apis-for-corresponding-flows). The documents on [Auth Scenarios](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-authentication-scenarios#application-types-and-scenarios) and [Auth protocols](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-openid-connect-code) are recommended reading. ## Versions This library follows [Semantic Versioning](https://semver.org/). You can find the changes for each version under [Releases](https://github.com/AzureAD/azure-activedirectory-library-for-python/releases). ## Community Help and Support We leverage [Stack Overflow](https://stackoverflow.com/) to work with the community on supporting Azure Active Directory and its SDKs, including this one! We highly recommend you ask your questions on Stack Overflow (we're all on there!) Also browser existing issues to see if someone has had your question before. We recommend you use the "adal" tag so we can see it! Here is the latest Q&A on Stack Overflow for ADAL: [https://stackoverflow.com/questions/tagged/adal](https://stackoverflow.com/questions/tagged/adal) ## Submit Feedback We'd like your thoughts on this library. Please complete [this short survey.](https://forms.office.com/r/wX0UuEF8kX) ## Security Reporting If you find a security issue with our libraries or services please report it to [secure@microsoft.com](mailto:secure@microsoft.com) with as much detail as possible. Your submission may be eligible for a bounty through the [Microsoft Bounty](https://aka.ms/bugbounty) program. Please do not post security issues to GitHub Issues or any other public site. We will contact you shortly upon receiving the information. We encourage you to get notifications of when security incidents occur by visiting [this page](https://technet.microsoft.com/en-us/security/dd252948) and subscribing to Security Advisory Alerts. ## Contributing All code is licensed under the MIT license and we triage actively on GitHub. We enthusiastically welcome contributions and feedback. Please read the [contributing guide](./contributing.md) before starting. ## We Value and Adhere to the Microsoft Open Source Code of Conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. azure-activedirectory-library-for-python-1.2.7/RELEASES.md000066400000000000000000000145361403263465100233540ustar00rootroot00000000000000# Microsoft Identity SDK Versioning and Servicing FAQ We have adopted the semantic versioning flow that is industry standard for OSS projects. It gives the maximum amount of control on what risk you take with what versions. If you know how semantic versioning works with node.js, java, and ruby none of this will be new. ##Semantic Versioning and API stability promises Microsoft Identity libraries are independent open source libraries that are used by partners both internal and external to Microsoft. As with the rest of Microsoft, we have moved to a rapid iteration model where bugs are fixed daily and new versions are produced as required. To communicate these frequent changes to external partners and customers, we use semantic versioning for all our public Microsoft Identity SDK libraries. This follows the practices of other open source libraries on the internet. This allows us to support our downstream partners which will lock on certain versions for stability purposes, as well as providing for the distribution over NuGet, CocoaPods, and Maven. The semantics are: MAJOR.MINOR.PATCH (example 1.1.5) We will update our code distributions to use the latest PATCH semantic version number in order to make sure our customers and partners get the latest bug fixes. Downstream partner needs to pull the latest PATCH version. Most partners should try lock on the latest MINOR version number in their builds and accept any updates in the PATCH number. Examples: Using Cocapods, the following in the podfile will take the latest ADALiOS build that is > 1.1 but not 1.2. ``` pod 'ADALiOS', '~> 1.1' ``` Using NuGet, this ensures all 1.1.0 to 1.1.x updates are included when building your code, but not 1.2. ``` ``` | Version | Description | Example | |:-------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------:| | x.x.x | PATCH version number. Incrementing these numbers is for bug fixes and updates but do not introduce new features. This is used for close partners who build on our platform release (ex. Azure AD Fabric, Office, etc.),In addition, Cocoapods, NuGet, and Maven use this number to deliver the latest release to customers.,This will update frequently (sometimes within the same day),There is no new features, and no regressions or API surface changes. Code will continue to work unless affected by a particular code fix. | ADAL for iOS 1.0.10,(this was a fix for the Storyboard display that was fixed for a specific Office team) | | x.x | MINOR version numbers. Incrementing these second numbers are for new feature additions that do not impact existing features or introduce regressions. They are purely additive, but may require testing to ensure nothing is impacted.,All x.x.x bug fixes will also roll up in to this number.,There is no regressions or API surface changes. Code will continue to work unless affected by a particular code fix or needs this new feature. | ADAL for iOS 1.1.0,(this added WPJ capability to ADAL, and rolled all the updates from 1.0.0 to 1.0.12) | | x | MAJOR version numbers. This should be considered a new, supported version of Microsoft Identity SDK and begins the Azure two year support cycle anew. Major new features are introduced and API changes can occur.,This should only be used after a large amount of testing and used only if those features are needed.,We will continue to service MAJOR version numbers with bug fixes up to the two year support cycle. | ADAL for iOS 1.0,(our first official release of ADAL) | ## Serviceability When we release a new MINOR version, the previous MINOR version is abandoned. When we release a new MAJOR version, we will continue to apply bug fixes to the existing features in the previous MAJOR version for up to the 2 year support cycle for Azure. Example: We release ADALiOS 2.0 in the future which supports unified Auth for AAD and MSA. Later, we then have a fix in Conditional Access for ADALiOS. Since that feature exists both in ADALiOS 1.1 and ADALiOS 2.0, we will fix both. It will roll up in a PATCH number for each. Customers that are still locked down on ADALiOS 1.1 will receive the benefit of this fix. ## Microsoft Identity SDKs and Azure Active Directory Microsoft Identity SDKs major versions will maintain backwards compatibility with Azure Active Directory web services through the support period. This means that the API surface area defined in a MAJOR version will continue to work for 2 years after release. We will respond to bugs quickly from our partners and customers submitted through GitHub and through our private alias (tellaad@microsoft.com) for security issues and update the PATCH version number. We will also submit a change summary for each PATCH number. Occasionally, there will be security bugs or breaking bugs from our partners that will require an immediate fix and a publish of an update to all partners and customers. When this occurs, we will do an emergency roll up to a PATCH version number and update all our distribution methods to the latest. azure-activedirectory-library-for-python-1.2.7/adal.pyproj000066400000000000000000000201761403263465100237720ustar00rootroot00000000000000 Debug 2.0 a3fd1cd9-55d1-4dc7-a7e3-124dcfd9c010 tests\__init__.py . . adal adal {4e6b690d-870d-4e48-b24e-ba6f9fe36da5} 3.5 true false true false Code Code Code Code Code Code Code Code Code Code Code Code Code Code Code Code Code Code Code Code {035af01c-b03d-490d-8832-e92e10c726f3} {2af0f10d-7135-4994-9156-5d01c9c11b7e} 2.7 env27 (Python 2.7) Scripts\python.exe Scripts\pythonw.exe Lib\ PYTHONPATH X86 {ffb82c48-90d8-4438-b920-50f90148b4d6} {2af0f10d-7135-4994-9156-5d01c9c11b7e} 3.4 env34 (Python 3.4) Scripts\python.exe Scripts\pythonw.exe Lib\ PYTHONPATH X86 {4e6b690d-870d-4e48-b24e-ba6f9fe36da5} {2af0f10d-7135-4994-9156-5d01c9c11b7e} 3.5 env35 (Python 3.5) Scripts\python.exe Scripts\pythonw.exe Lib\ PYTHONPATH X86 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets azure-activedirectory-library-for-python-1.2.7/adal.sln000066400000000000000000000014541403263465100232410ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "ADAL", "adal.pyproj", "{A3FD1CD9-55D1-4DC7-A7E3-124DCFD9C010}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {A3FD1CD9-55D1-4DC7-A7E3-124DCFD9C010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A3FD1CD9-55D1-4DC7-A7E3-124DCFD9C010}.Release|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal azure-activedirectory-library-for-python-1.2.7/adal/000077500000000000000000000000001403263465100225175ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/adal/__init__.py000066400000000000000000000034361403263465100246360ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ # pylint: disable=wrong-import-position __version__ = '1.2.7' import logging from .authentication_context import AuthenticationContext from .token_cache import TokenCache from .log import (set_logging_options, get_logging_options, ADAL_LOGGER_NAME) from .adal_error import AdalError # to avoid "No handler found" warnings. logging.getLogger(ADAL_LOGGER_NAME).addHandler(logging.NullHandler()) azure-activedirectory-library-for-python-1.2.7/adal/adal_error.py000066400000000000000000000027621403263465100252120ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ class AdalError(Exception): def __init__(self, error_msg, error_response=None): super(AdalError, self).__init__(error_msg) self.error_response = error_response azure-activedirectory-library-for-python-1.2.7/adal/argument.py000066400000000000000000000037471403263465100247260ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ from .constants import OAuth2DeviceCodeResponseParameters def validate_user_code_info(user_code_info): if not user_code_info: raise ValueError("the user_code_info parameter is required") if not user_code_info.get(OAuth2DeviceCodeResponseParameters.DEVICE_CODE): raise ValueError("the user_code_info is missing device_code") if not user_code_info.get(OAuth2DeviceCodeResponseParameters.INTERVAL): raise ValueError("the user_code_info is missing internal") if not user_code_info.get(OAuth2DeviceCodeResponseParameters.EXPIRES_IN): raise ValueError("the user_code_info is missing expires_in") azure-activedirectory-library-for-python-1.2.7/adal/authentication_context.py000066400000000000000000000402431403263465100276570ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 os import threading import warnings from .authority import Authority from . import argument from .code_request import CodeRequest from .token_request import TokenRequest from .token_cache import TokenCache from . import log from .constants import OAuth2DeviceCodeResponseParameters GLOBAL_ADAL_OPTIONS = {} class AuthenticationContext(object): '''Retrieves authentication tokens from Azure Active Directory. For usages, check out the "sample" folder at: https://github.com/AzureAD/azure-activedirectory-library-for-python ''' def __init__( self, authority, validate_authority=None, cache=None, api_version=None, timeout=None, enable_pii=False, verify_ssl=None, proxies=None): '''Creates a new AuthenticationContext object. By default the authority will be checked against a list of known Azure Active Directory authorities. If the authority is not recognized as one of these well known authorities then token acquisition will fail. This behavior can be turned off via the validate_authority parameter below. :param str authority: A URL that identifies a token authority. It should be of the format https://login.microsoftonline.com/your_tenant :param bool validate_authority: (optional) Turns authority validation on or off. This parameter default to true. :param TokenCache cache: (optional) Sets the token cache used by this AuthenticationContext instance. If this parameter is not set, then a default is used. Cache instances is only used by that instance of the AuthenticationContext and are not shared unless it has been manually passed during the construction of other AuthenticationContexts. :param api_version: (optional) Specifies API version using on the wire. Historically it has a hardcoded default value as "1.0". Developers have been encouraged to set it as None explicitly, which means the underlying API version will be automatically chosen. Starting from ADAL Python 1.0, this default value becomes None. :param timeout: (optional) requests timeout. How long to wait for the server to send data before giving up, as a float, or a `(connect timeout, read timeout) ` tuple. :param enable_pii: (optional) Unless this is set to True, there will be no Personally Identifiable Information (PII) written in log. :param verify_ssl: (optional) requests verify. Either a boolean, in which case it controls whether we verify the server's TLS certificate, or a string, in which case it must be a path to a CA bundle to use. If this value is not provided, and ADAL_PYTHON_SSL_NO_VERIFY env varaible is set, behavior is equivalent to verify_ssl=False. :param proxies: (optional) requests proxies. Dictionary mapping protocol to the URL of the proxy. See http://docs.python-requests.org/en/master/user/advanced/#proxies for details. ''' self.authority = Authority(authority, validate_authority is None or validate_authority) self._oauth2client = None self.correlation_id = None env_verify = 'ADAL_PYTHON_SSL_NO_VERIFY' not in os.environ verify = verify_ssl if verify_ssl is not None else env_verify if api_version is not None: warnings.warn( """The default behavior of including api-version=1.0 on the wire is now deprecated. Future version of ADAL will change the default value to None. To ensure a smooth transition, you are recommended to explicitly set it to None in your code now, and test out the new behavior. context = AuthenticationContext(..., api_version=None) """, DeprecationWarning) self._call_context = { 'options': GLOBAL_ADAL_OPTIONS, 'api_version': api_version, 'verify_ssl': verify, 'proxies':proxies, 'timeout':timeout, "enable_pii": enable_pii, } self._token_requests_with_user_code = {} self.cache = cache or TokenCache() self._lock = threading.RLock() @property def options(self): return self._call_context['options'] @options.setter def options(self, val): self._call_context['options'] = val def _acquire_token(self, token_func, correlation_id=None): self._call_context['log_context'] = log.create_log_context( correlation_id or self.correlation_id, self._call_context.get('enable_pii', False)) self.authority.validate(self._call_context) return token_func(self) def acquire_token(self, resource, user_id, client_id): '''Gets a token for a given resource via cached tokens. :param str resource: A URI that identifies the resource for which the token is valid. :param str user_id: The username of the user on behalf this application is authenticating. :param str client_id: The OAuth client id of the calling application. :returns: dic with several keys, include "accessToken" and "refreshToken". ''' def token_func(self): token_request = TokenRequest(self._call_context, self, client_id, resource) return token_request.get_token_from_cache_with_refresh(user_id) return self._acquire_token(token_func) def acquire_token_with_username_password(self, resource, username, password, client_id): '''Gets a token for a given resource via user credentails. :param str resource: A URI that identifies the resource for which the token is valid. :param str username: The username of the user on behalf this application is authenticating. :param str password: The password of the user named in the username parameter. :param str client_id: The OAuth client id of the calling application. :returns: dict with several keys, include "accessToken" and "refreshToken". ''' def token_func(self): token_request = TokenRequest(self._call_context, self, client_id, resource) return token_request.get_token_with_username_password(username, password) return self._acquire_token(token_func) def acquire_token_with_client_credentials(self, resource, client_id, client_secret): '''Gets a token for a given resource via client credentials. :param str resource: A URI that identifies the resource for which the token is valid. :param str client_id: The OAuth client id of the calling application. :param str client_secret: The OAuth client secret of the calling application. :returns: dict with several keys, include "accessToken". ''' def token_func(self): token_request = TokenRequest(self._call_context, self, client_id, resource) return token_request.get_token_with_client_credentials(client_secret) return self._acquire_token(token_func) def acquire_token_with_authorization_code(self, authorization_code, redirect_uri, resource, client_id, client_secret=None, code_verifier=None): '''Gets a token for a given resource via authorization code for a server app. :param str authorization_code: An authorization code returned from a client. :param str redirect_uri: the redirect uri that was used in the authorize call. :param str resource: A URI that identifies the resource for which the token is valid. :param str client_id: The OAuth client id of the calling application. :param str client_secret: (only for confidential clients)The OAuth client secret of the calling application. This parameter if not set, defaults to None :param str code_verifier: (optional)The code verifier that was used to obtain authorization code if PKCE was used in the authorization code grant request.(usually used by public clients) This parameter if not set, defaults to None :returns: dict with several keys, include "accessToken" and "refreshToken". ''' def token_func(self): token_request = TokenRequest( self._call_context, self, client_id, resource, redirect_uri) return token_request.get_token_with_authorization_code( authorization_code, client_secret, code_verifier) return self._acquire_token(token_func) def acquire_token_with_refresh_token(self, refresh_token, client_id, resource, client_secret=None): '''Gets a token for a given resource via refresh tokens :param str refresh_token: A refresh token returned in a tokne response from a previous invocation of acquireToken. :param str client_id: The OAuth client id of the calling application. :param str resource: A URI that identifies the resource for which the token is valid. :param str client_secret: (optional)The OAuth client secret of the calling application. :returns: dict with several keys, include "accessToken" and "refreshToken". ''' def token_func(self): token_request = TokenRequest(self._call_context, self, client_id, resource) return token_request.get_token_with_refresh_token(refresh_token, client_secret) return self._acquire_token(token_func) def acquire_token_with_client_certificate(self, resource, client_id, certificate, thumbprint, public_certificate=None): '''Gets a token for a given resource via certificate credentials :param str resource: A URI that identifies the resource for which the token is valid. :param str client_id: The OAuth client id of the calling application. :param str certificate: A PEM encoded certificate private key. :param str thumbprint: hex encoded thumbprint of the certificate. :param str public_certificate(optional): if not None, it will be sent to the service for subject name and issuer based authentication, which is to support cert auto rolls. The value must match the certificate private key parameter. Per `specs `_, "the certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the one used to certify the previous one." However, your certificate's issuer may use a different order. So, if your attempt ends up with an error AADSTS700027 - "The provided signature value did not match the expected signature value", you may try use only the leaf cert (in PEM/str format) instead. :returns: dict with several keys, include "accessToken". ''' def token_func(self): token_request = TokenRequest(self._call_context, self, client_id, resource) return token_request.get_token_with_certificate(certificate, thumbprint, public_certificate) return self._acquire_token(token_func) def acquire_user_code(self, resource, client_id, language=None): '''Gets the user code info which contains user_code, device_code for authenticating user on device. :param str resource: A URI that identifies the resource for which the device_code and user_code is valid for. :param str client_id: The OAuth client id of the calling application. :param str language: The language code specifying how the message should be localized to. :returns: dict contains code and uri for users to login through browser. ''' self._call_context['log_context'] = log.create_log_context( self.correlation_id, self._call_context.get('enable_pii', False)) self.authority.validate(self._call_context) code_request = CodeRequest(self._call_context, self, client_id, resource) return code_request.get_user_code_info(language) def acquire_token_with_device_code(self, resource, user_code_info, client_id): '''Gets a new access token using via a device code. :param str resource: A URI that identifies the resource for which the token is valid. :param dict user_code_info: The code info from the invocation of "acquire_user_code" :param str client_id: The OAuth client id of the calling application. :returns: dict with several keys, include "accessToken" and "refreshToken". ''' def token_func(self): token_request = TokenRequest(self._call_context, self, client_id, resource) key = user_code_info[OAuth2DeviceCodeResponseParameters.DEVICE_CODE] with self._lock: self._token_requests_with_user_code[key] = token_request token = token_request.get_token_with_device_code(user_code_info) with self._lock: self._token_requests_with_user_code.pop(key, None) return token return self._acquire_token(token_func, user_code_info.get('correlation_id', None)) def cancel_request_to_get_token_with_device_code(self, user_code_info): '''Cancels the polling request to get token with device code. :param dict user_code_info: The code info from the invocation of "acquire_user_code" :returns: None ''' argument.validate_user_code_info(user_code_info) key = user_code_info[OAuth2DeviceCodeResponseParameters.DEVICE_CODE] with self._lock: request = self._token_requests_with_user_code.get(key) if not request: raise ValueError('No acquire_token_with_device_code existed to be cancelled') request.cancel_token_request_with_device_code() self._token_requests_with_user_code.pop(key, None) azure-activedirectory-library-for-python-1.2.7/adal/authentication_parameters.py000066400000000000000000000200131403263465100303270ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ #Note, this module does not appear being used anywhere import re import requests from . import util from . import log from .constants import HttpError AUTHORIZATION_URI = 'authorization_uri' RESOURCE = 'resource' WWW_AUTHENTICATE_HEADER = 'www-authenticate' # pylint: disable=anomalous-backslash-in-string,too-few-public-methods class AuthenticationParameters(object): def __init__(self, authorization_uri, resource): self.authorization_uri = authorization_uri self.resource = resource # The 401 challenge is a standard defined in RFC6750, which is based in part on RFC2617. # The challenge has the following form. # WWW-Authenticate : Bearer # authorization_uri="https://login.microsoftonline.com/mytenant.com/oauth2/authorize", # Resource_id="00000002-0000-0000-c000-000000000000" # This regex is used to validate the structure of the challenge header. # Match whole structure: ^\s*Bearer\s+([^,\s="]+?)="([^"]*?)"\s*(,\s*([^,\s="]+?)="([^"]*?)"\s*)*$ # ^ Start at the beginning of the string. # \s*Bearer\s+ Match 'Bearer' surrounded by one or more amount of whitespace. # ([^,\s="]+?) This captures the key which is composed of any characters except # comma, whitespace or a quotes. # = Match the = sign. # "([^"]*?)" Captures the value can be any number of non quote characters. # At this point only the first key value pair as been captured. # \s* There can be any amount of white space after the first key value pair. # ( Start a capture group to retrieve the rest of the key value # pairs that are separated by commas. # \s* There can be any amount of whitespace before the comma. # , There must be a comma. # \s* There can be any amount of whitespace after the comma. # (([^,\s="]+?) This will capture the key that comes after the comma. It's made # of a series of any character except comma, whitespace or quotes. # = Match the equal sign between the key and value. # " Match the opening quote of the value. # ([^"]*?) This will capture the value which can be any number of non # quote characters. # " Match the values closing quote. # \s* There can be any amount of whitespace before the next comma. # )* Close the capture group for key value pairs. There can be any # number of these. # $ The rest of the string can be whitespace but nothing else up to # the end of the string. # # This regex checks the structure of the whole challenge header. The complete # header needs to be checked for validity before we can be certain that # we will succeed in pulling out the individual parts. bearer_challenge_structure_validation = re.compile( """^\s*Bearer\s+([^,\s="]+?)="([^"]*?)"\s*(,\s*([^,\s="]+?)="([^"]*?)"\s*)*$""") # This regex pulls out the key and value from the very first pair. first_key_value_pair_regex = re.compile("""^\s*Bearer\s+([^,\s="]+?)="([^"]*?)"\s*""") # This regex is used to pull out all of the key value pairs after the first one. # All of these begin with a comma. all_other_key_value_pair_regex = re.compile("""(?:,\s*([^,\s="]+?)="([^"]*?)"\s*)""") def parse_challenge(challenge): if not bearer_challenge_structure_validation.search(challenge): raise ValueError("The challenge is not parseable as an RFC6750 OAuth2 challenge") challenge_parameters = {} match = first_key_value_pair_regex.search(challenge) if match: challenge_parameters[match.group(1)] = match.group(2) for match in all_other_key_value_pair_regex.finditer(challenge): challenge_parameters[match.group(1)] = match.group(2) return challenge_parameters def create_authentication_parameters_from_header(challenge): challenge_parameters = parse_challenge(challenge) authorization_uri = challenge_parameters.get(AUTHORIZATION_URI) if not authorization_uri: raise ValueError("Could not find 'authorization_uri' in challenge header.") resource = challenge_parameters.get(RESOURCE) return AuthenticationParameters(authorization_uri, resource) def create_authentication_parameters_from_response(response): if response is None: raise AttributeError('Missing required parameter: response') if not hasattr(response, 'status_code') or not response.status_code: raise AttributeError('The response parameter does not have the expected HTTP status_code field') if not hasattr(response, 'headers') or not response.headers: raise AttributeError('There were no headers found in the response.') if response.status_code != HttpError.UNAUTHORIZED: raise ValueError('The response status code does not correspond to an OAuth challenge. ' 'The statusCode is expected to be 401 but is: {}'.format(response.status_code)) challenge = response.headers.get(WWW_AUTHENTICATE_HEADER) if not challenge: raise ValueError("The response does not contain a WWW-Authenticate header that can be " "used to determine the authority_uri and resource.") return create_authentication_parameters_from_header(challenge) def validate_url_object(url): if not url or not hasattr(url, 'geturl'): raise AttributeError('Parameter is of wrong type: url') def create_authentication_parameters_from_url(url, correlation_id=None): if isinstance(url, str): challenge_url = url else: validate_url_object(url) challenge_url = url.geturl() log_context = log.create_log_context(correlation_id) logger = log.Logger('AuthenticationParameters', log_context) logger.debug( "Attempting to retrieve authentication parameters from: {}".format(challenge_url) ) class _options(object): _call_context = {'log_context': log_context} options = util.create_request_options(_options()) try: response = requests.get(challenge_url, headers=options['headers']) except Exception: logger.info("Authentication parameters http get failed.") raise try: return create_authentication_parameters_from_response(response) except Exception: logger.info("Unable to parse response in to authentication parameters.") raise azure-activedirectory-library-for-python-1.2.7/adal/authority.py000066400000000000000000000204661403263465100251310ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ try: from urllib.parse import quote, urlparse except ImportError: from urllib import quote # pylint: disable=no-name-in-module from urlparse import urlparse # pylint: disable=import-error,ungrouped-imports import requests from .constants import AADConstants from .adal_error import AdalError from . import log from . import util class Authority(object): def __init__(self, authority_url, validate_authority=True): self._log = None self._call_context = None self._url = urlparse(authority_url) self._validate_authority_url() self._validated = not validate_authority self._host = None self._tenant = None self._parse_authority() self._authorization_endpoint = None self.token_endpoint = None self.device_code_endpoint = None self.is_adfs_authority = self._tenant.lower() == 'adfs' @property def url(self): return self._url.geturl() def _whitelisted(self): # testing if self._url.hostname is a dsts whitelisted domain # Add dSTS domains to whitelist based on based on domain # https://microsoft.sharepoint.com/teams/AzureSecurityCompliance/Security/SitePages/dSTS%20Fundamentals.aspx return ".dsts." in self._url.hostname def _validate_authority_url(self): if self._url.scheme != 'https': raise ValueError("The authority url must be an https endpoint.") if self._url.query: raise ValueError("The authority url must not have a query string.") path_parts = [part for part in self._url.path.split('/') if part] if (len(path_parts) > 1) and (not self._whitelisted()): #if dsts host, path_parts will be 2 raise ValueError( "The path of authority_url (also known as tenant) is invalid, " "it should either be a domain name (e.g. mycompany.onmicrosoft.com) " "or a tenant GUID id. " 'Your tenant input was "%s" and your entire authority_url was "%s".' % ('/'.join(path_parts), self._url.geturl())) elif len(path_parts) == 1: self._url = urlparse(self._url.geturl().rstrip('/')) def _parse_authority(self): self._host = self._url.hostname path_parts = self._url.path.split('/') try: self._tenant = path_parts[1] except IndexError: raise ValueError("Could not determine tenant.") def _perform_static_instance_discovery(self): self._log.debug("Performing static instance discovery") if self._whitelisted(): # testing if self._url.hostname is a dsts whitelisted domain self._log.debug("Authority validated via static instance discovery") return True try: AADConstants.WELL_KNOWN_AUTHORITY_HOSTS.index(self._url.hostname) except ValueError: return False self._log.debug("Authority validated via static instance discovery") return True def _create_authority_url(self): return "https://{}/{}{}".format(self._url.hostname, self._tenant, AADConstants.AUTHORIZE_ENDPOINT_PATH) def _create_instance_discovery_endpoint_from_template(self, authority_host): discovery_endpoint = AADConstants.INSTANCE_DISCOVERY_ENDPOINT_TEMPLATE discovery_endpoint = discovery_endpoint.replace('{authorize_host}', authority_host) discovery_endpoint = discovery_endpoint.replace('{authorize_endpoint}', quote(self._create_authority_url(), safe='~()*!.\'')) return urlparse(discovery_endpoint) def _perform_dynamic_instance_discovery(self): discovery_endpoint = self._create_instance_discovery_endpoint_from_template( AADConstants.WORLD_WIDE_AUTHORITY) get_options = util.create_request_options(self) operation = "Instance Discovery" self._log.debug("Attempting instance discover at: %(discovery_endpoint)s", {"discovery_endpoint": discovery_endpoint.geturl()}) try: resp = requests.get(discovery_endpoint.geturl(), headers=get_options['headers'], verify=self._call_context.get('verify_ssl', None), proxies=self._call_context.get('proxies', None)) util.log_return_correlation_id(self._log, operation, resp) except Exception: self._log.exception("%(operation)s request failed", {"operation": operation}) raise if resp.status_code == 429: resp.raise_for_status() # Will raise requests.exceptions.HTTPError if not util.is_http_success(resp.status_code): return_error_string = u"{} request returned http error: {}".format(operation, resp.status_code) error_response = "" if resp.text: return_error_string = u"{} and server response: {}".format(return_error_string, resp.text) try: error_response = resp.json() except ValueError: pass raise AdalError(return_error_string, error_response) else: discovery_resp = resp.json() if discovery_resp.get('tenant_discovery_endpoint'): return discovery_resp['tenant_discovery_endpoint'] else: raise AdalError('Failed to parse instance discovery response') def _validate_via_instance_discovery(self): valid = self._perform_static_instance_discovery() if not valid: self._perform_dynamic_instance_discovery() def _get_oauth_endpoints(self): if (not self.token_endpoint) or (not self.device_code_endpoint): self.token_endpoint = self._url.geturl() + AADConstants.TOKEN_ENDPOINT_PATH self.device_code_endpoint = self._url.geturl() + AADConstants.DEVICE_ENDPOINT_PATH def validate(self, call_context): self._log = log.Logger('Authority', call_context['log_context']) self._call_context = call_context if not self._validated: self._log.debug("Performing instance discovery: %(authority)s", {"authority": self._url.geturl()}) self._validate_via_instance_discovery() self._validated = True else: self._log.debug( "Instance discovery/validation has either already been completed or is turned off: %(authority)s", {"authority": self._url.geturl()}) self._get_oauth_endpoints() azure-activedirectory-library-for-python-1.2.7/adal/cache_driver.py000066400000000000000000000244021403263465100255110ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 base64 import copy import hashlib from datetime import datetime, timedelta from dateutil import parser from .adal_error import AdalError from .constants import TokenResponseFields, Misc from . import log #surppress warnings: like accces to a protected member of "_AUTHORITY", etc # pylint: disable=W0212 def _create_token_hash(token): hash_object = hashlib.sha256() hash_object.update(token.encode('utf8')) return base64.b64encode(hash_object.digest()) def _create_token_id_message(entry): access_token_hash = _create_token_hash(entry[TokenResponseFields.ACCESS_TOKEN]) message = 'AccessTokenId: ' + str(access_token_hash) if entry.get(TokenResponseFields.REFRESH_TOKEN): refresh_token_hash = _create_token_hash(entry[TokenResponseFields.REFRESH_TOKEN]) message += ', RefreshTokenId: ' + str(refresh_token_hash) return message def _is_mrrt(entry): return bool(entry.get(TokenResponseFields.RESOURCE, None)) def _entry_has_metadata(entry): return (TokenResponseFields._CLIENT_ID in entry and TokenResponseFields._AUTHORITY in entry) class CacheDriver(object): def __init__(self, call_context, authority, resource, client_id, cache, refresh_function): self._call_context = call_context self._log = log.Logger("CacheDriver", call_context['log_context']) self._authority = authority self._resource = resource self._client_id = client_id self._cache = cache self._refresh_function = refresh_function def _get_potential_entries(self, query): potential_entries_query = {} if query.get(TokenResponseFields._CLIENT_ID): potential_entries_query[TokenResponseFields._CLIENT_ID] = query[TokenResponseFields._CLIENT_ID] if query.get(TokenResponseFields.USER_ID): potential_entries_query[TokenResponseFields.USER_ID] = query[TokenResponseFields.USER_ID] self._log.debug( 'Looking for potential cache entries: %(query)s', {"query": log.scrub_pii(potential_entries_query)}) entries = self._cache.find(potential_entries_query) self._log.debug( 'Found %(quantity)s potential entries.', {"quantity": len(entries)}) return entries def _find_mrrt_tokens_for_user(self, user): return self._cache.find({ TokenResponseFields.IS_MRRT: True, TokenResponseFields.USER_ID: user, TokenResponseFields._CLIENT_ID : self._client_id }) def _load_single_entry_from_cache(self, query): return_val = [] is_resource_tenant_specific = False potential_entries = self._get_potential_entries(query) if potential_entries: resource_tenant_specific_entries = [ x for x in potential_entries if x[TokenResponseFields.RESOURCE] == self._resource and x[TokenResponseFields._AUTHORITY] == self._authority] if not resource_tenant_specific_entries: self._log.debug('No resource specific cache entries found.') #There are no resource specific entries. Find an MRRT token. mrrt_tokens = (x for x in potential_entries if x[TokenResponseFields.IS_MRRT]) token = next(mrrt_tokens, None) if token: self._log.debug('Found an MRRT token.') return_val = token else: self._log.debug('No MRRT tokens found.') elif len(resource_tenant_specific_entries) == 1: self._log.debug('Resource specific token found.') return_val = resource_tenant_specific_entries[0] is_resource_tenant_specific = True else: raise AdalError('More than one token matches the criteria. The result is ambiguous.') if return_val: self._log.debug('Returning token from cache lookup, %(token_hash)s', {"token_hash": _create_token_id_message(return_val)}) return return_val, is_resource_tenant_specific 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 self._log.debug('Created new cache entry from refresh response.') return new_entry def _replace_entry(self, entry_to_replace, new_entry): self.remove(entry_to_replace) self.add(new_entry) def _refresh_expired_entry(self, entry): token_response = self._refresh_function(entry, None) new_entry = self._create_entry_from_refresh(entry, token_response) self._replace_entry(entry, new_entry) self._log.info('Returning token refreshed after expiry.') return new_entry def _acquire_new_token_from_mrrt(self, entry): token_response = self._refresh_function(entry, self._resource) new_entry = self._create_entry_from_refresh(entry, token_response) self.add(new_entry) self._log.info('Returning token derived from mrrt refresh.') return new_entry def _refresh_entry_if_necessary(self, entry, is_resource_specific): expiry_date = parser.parse(entry[TokenResponseFields.EXPIRES_ON]) now = datetime.now(expiry_date.tzinfo) # Add some buffer in to the time comparison to account for clock skew or latency. now_plus_buffer = now + timedelta(minutes=Misc.CLOCK_BUFFER) if is_resource_specific and now_plus_buffer > expiry_date: if TokenResponseFields.REFRESH_TOKEN in entry: self._log.info('Cached token is expired at %(date)s. Refreshing', {"date": 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): 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 def find(self, query): if query is None: query = {} self._log.debug('finding with query keys: %(query)s', {"query": log.scrub_pii(query)}) entry, is_resource_tenant_specific = self._load_single_entry_from_cache(query) if entry: return self._refresh_entry_if_necessary(entry, is_resource_tenant_specific) else: return None def remove(self, entry): self._log.debug('Removing entry.') self._cache.remove([entry]) def _remove_many(self, entries): self._log.debug('Remove many: %(number)s', {"number": len(entries)}) self._cache.remove(entries) def _add_many(self, entries): self._log.debug('Add many: %(number)s', {"number": len(entries)}) self._cache.add(entries) def _update_refresh_tokens(self, entry): if _is_mrrt(entry) and entry.get(TokenResponseFields.REFRESH_TOKEN): mrrt_tokens = self._find_mrrt_tokens_for_user(entry.get(TokenResponseFields.USER_ID)) if mrrt_tokens: self._log.debug('Updating %(number)s cached refresh tokens', {"number": len(mrrt_tokens)}) self._remove_many(mrrt_tokens) for t in mrrt_tokens: t[TokenResponseFields.REFRESH_TOKEN] = entry[TokenResponseFields.REFRESH_TOKEN] self._add_many(mrrt_tokens) def _argument_entry_with_cached_metadata(self, entry): if _entry_has_metadata(entry): return if _is_mrrt(entry): self._log.debug('Added entry is MRRT') entry[TokenResponseFields.IS_MRRT] = True else: entry[TokenResponseFields.RESOURCE] = self._resource entry[TokenResponseFields._CLIENT_ID] = self._client_id entry[TokenResponseFields._AUTHORITY] = self._authority def add(self, entry): self._log.debug('Adding entry %(token_hash)s', {"token_hash": _create_token_id_message(entry)}) self._argument_entry_with_cached_metadata(entry) self._update_refresh_tokens(entry) self._cache.add([entry]) azure-activedirectory-library-for-python-1.2.7/adal/code_request.py000066400000000000000000000051271403263465100255600ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ from . import constants from . import log from . import oauth2_client OAUTH2_PARAMETERS = constants.OAuth2.Parameters class CodeRequest(object): def __init__(self, call_context, authentication_context, client_id, resource): self._log = log.Logger("CodeRequest", call_context['log_context']) self._call_context = call_context self._authentication_context = authentication_context self._client_id = client_id self._resource = resource def _get_user_code_info(self, oauth_parameters): client = self._create_oauth2_client() return client.get_user_code_info(oauth_parameters) def _create_oauth2_client(self): return oauth2_client.OAuth2Client( self._call_context, self._authentication_context.authority) def _create_oauth_parameters(self): return { OAUTH2_PARAMETERS.CLIENT_ID: self._client_id, OAUTH2_PARAMETERS.RESOURCE: self._resource } def get_user_code_info(self, language): self._log.info('Getting user code info.') oauth_parameters = self._create_oauth_parameters() if language: oauth_parameters[OAUTH2_PARAMETERS.LANGUAGE] = language return self._get_user_code_info(oauth_parameters) azure-activedirectory-library-for-python-1.2.7/adal/constants.py000066400000000000000000000167721403263465100251220ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ # pylint: disable=too-few-public-methods,old-style-class,no-init class Errors: # Constants ERROR_VALUE_NONE = '{} should not be None.' ERROR_VALUE_EMPTY_STRING = '{} should not be "".' ERROR_RESPONSE_MALFORMED_XML = 'The provided response string is not well formed XML.' class OAuth2Parameters(object): GRANT_TYPE = 'grant_type' CLIENT_ASSERTION = 'client_assertion' CLIENT_ASSERTION_TYPE = 'client_assertion_type' CLIENT_ID = 'client_id' CLIENT_SECRET = 'client_secret' REDIRECT_URI = 'redirect_uri' RESOURCE = 'resource' CODE = 'code' CODE_VERIFIER = 'code_verifier' SCOPE = 'scope' ASSERTION = 'assertion' AAD_API_VERSION = 'api-version' USERNAME = 'username' PASSWORD = 'password' REFRESH_TOKEN = 'refresh_token' LANGUAGE = 'mkt' DEVICE_CODE = 'device_code' class OAuth2GrantType(object): AUTHORIZATION_CODE = 'authorization_code' REFRESH_TOKEN = 'refresh_token' CLIENT_CREDENTIALS = 'client_credentials' JWT_BEARER = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' PASSWORD = 'password' SAML1 = 'urn:ietf:params:oauth:grant-type:saml1_1-bearer' SAML2 = 'urn:ietf:params:oauth:grant-type:saml2-bearer' DEVICE_CODE = 'device_code' class OAuth2ResponseParameters(object): CODE = 'code' TOKEN_TYPE = 'token_type' ACCESS_TOKEN = 'access_token' ID_TOKEN = 'id_token' REFRESH_TOKEN = 'refresh_token' CREATED_ON = 'created_on' EXPIRES_ON = 'expires_on' EXPIRES_IN = 'expires_in' RESOURCE = 'resource' ERROR = 'error' ERROR_DESCRIPTION = 'error_description' class OAuth2DeviceCodeResponseParameters: USER_CODE = 'user_code' DEVICE_CODE = 'device_code' VERIFICATION_URL = 'verification_url' EXPIRES_IN = 'expires_in' INTERVAL = 'interval' MESSAGE = 'message' ERROR = 'error' ERROR_DESCRIPTION = 'error_description' class OAuth2Scope(object): OPENID = 'openid' class OAuth2(object): Parameters = OAuth2Parameters() GrantType = OAuth2GrantType() ResponseParameters = OAuth2ResponseParameters() DeviceCodeResponseParameters = OAuth2DeviceCodeResponseParameters() Scope = OAuth2Scope() IdTokenMap = { 'tid' : 'tenantId', 'given_name' : 'givenName', 'family_name' : 'familyName', 'idp' : 'identityProvider', 'oid' : 'oid' } class TokenResponseFields(object): TOKEN_TYPE = 'tokenType' ACCESS_TOKEN = 'accessToken' REFRESH_TOKEN = 'refreshToken' CREATED_ON = 'createdOn' EXPIRES_ON = 'expiresOn' EXPIRES_IN = 'expiresIn' RESOURCE = 'resource' USER_ID = 'userId' ERROR = 'error' ERROR_DESCRIPTION = 'errorDescription' # not from the wire, but amends for token cache _AUTHORITY = '_authority' _CLIENT_ID = '_clientId' IS_MRRT = 'isMRRT' class IdTokenFields(object): USER_ID = 'userId' IS_USER_ID_DISPLAYABLE = 'isUserIdDisplayable' TENANT_ID = 'tenantId' GIVE_NAME = 'givenName' FAMILY_NAME = 'familyName' IDENTITY_PROVIDER = 'identityProvider' class Misc(object): MAX_DATE = 0xffffffff CLOCK_BUFFER = 5 # In minutes. class Jwt(object): SELF_SIGNED_JWT_LIFETIME = 10 # 10 mins in mins AUDIENCE = 'aud' ISSUER = 'iss' SUBJECT = 'sub' NOT_BEFORE = 'nbf' EXPIRES_ON = 'exp' JWT_ID = 'jti' class UserRealm(object): federation_protocol_type = { 'WSFederation' : 'wstrust', 'SAML2' : 'saml20', 'Unknown' : 'unknown' } account_type = { 'Federated' : 'federated', 'Managed' : 'managed', 'Unknown' : 'unknown' } class Saml(object): TokenTypeV1 = 'urn:oasis:names:tc:SAML:1.0:assertion' TokenTypeV2 = 'urn:oasis:names:tc:SAML:2.0:assertion' OasisWssSaml11TokenProfile11 = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1" OasisWssSaml2TokenProfile2 = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0" class XmlNamespaces(object): namespaces = { 'wsdl' :'http://schemas.xmlsoap.org/wsdl/', 'sp' :'http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702', 'sp2005' :'http://schemas.xmlsoap.org/ws/2005/07/securitypolicy', 'wsu' :'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd', 'wsa10' :'http://www.w3.org/2005/08/addressing', 'http' :'http://schemas.microsoft.com/ws/06/2004/policy/http', 'soap12' :'http://schemas.xmlsoap.org/wsdl/soap12/', 'wsp' :'http://schemas.xmlsoap.org/ws/2004/09/policy', 's' :'http://www.w3.org/2003/05/soap-envelope', 'wsa' :'http://www.w3.org/2005/08/addressing', 'wst' :'http://docs.oasis-open.org/ws-sx/ws-trust/200512', 'trust' : "http://docs.oasis-open.org/ws-sx/ws-trust/200512", 'saml' : "urn:oasis:names:tc:SAML:1.0:assertion", 't' : 'http://schemas.xmlsoap.org/ws/2005/02/trust' } class Cache(object): HASH_ALGORITHM = 'sha256' class HttpError(object): UNAUTHORIZED = 401 class AADConstants(object): WORLD_WIDE_AUTHORITY = 'login.microsoftonline.com' WELL_KNOWN_AUTHORITY_HOSTS = [ 'login.windows.net', 'login.microsoftonline.com', 'login.chinacloudapi.cn', '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' DEVICE_ENDPOINT_PATH = '/oauth2/devicecode' class AdalIdParameters(object): SKU = 'x-client-SKU' VERSION = 'x-client-Ver' OS = 'x-client-OS' # pylint: disable=invalid-name CPU = 'x-client-CPU' PYTHON_SKU = 'Python' class WSTrustVersion(object): UNDEFINED = 'undefined' WSTRUST13 = 'wstrust13' WSTRUST2005 = 'wstrust2005' azure-activedirectory-library-for-python-1.2.7/adal/log.py000066400000000000000000000140001403263465100236450ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 logging import uuid import traceback ADAL_LOGGER_NAME = 'adal-python' def create_log_context(correlation_id=None, enable_pii=False): return { 'correlation_id' : correlation_id or str(uuid.uuid4()), 'enable_pii': enable_pii} def set_logging_options(options=None): '''Configure adal logger, including level and handler spec'd by python logging module. Basic Usages:: >>>adal.set_logging_options({ >>> 'level': 'DEBUG', >>> 'handler': logging.FileHandler('adal.log') >>>}) ''' if options is None: options = {} logger = logging.getLogger(ADAL_LOGGER_NAME) logger.setLevel(options.get('level', logging.ERROR)) handler = options.get('handler') if handler: handler.setLevel(logger.level) logger.addHandler(handler) def get_logging_options(): '''Get logging options :returns: a dict, with a key of 'level' for logging level. ''' logger = logging.getLogger(ADAL_LOGGER_NAME) level = logger.getEffectiveLevel() return { 'level': logging.getLevelName(level) } class Logger(object): '''wrapper around python built-in logging to log correlation_id, and stack trace through keyword argument of 'log_stack_trace' ''' def __init__(self, component_name, log_context): if not log_context: raise AttributeError('Logger: log_context is a required parameter') self._component_name = component_name self.log_context = log_context self._logging = logging.getLogger(ADAL_LOGGER_NAME) def _log_message(self, msg, log_stack_trace=None): correlation_id = self.log_context.get("correlation_id", "") formatted = "{} - {}:{}".format( correlation_id, self._component_name, msg) if log_stack_trace: formatted += "\nStack:\n{}".format(traceback.format_stack()) return formatted def warn(self, msg, *args, **kwargs): """ The recommended way to call this function with variable content, is to use the `warn("hello %(name)s", {"name": "John Doe"}` form, so that this method will scrub pii value when needed. """ if len(args) == 1 and isinstance(args[0], dict) and not self.log_context.get('enable_pii'): args = (scrub_pii(args[0]),) log_stack_trace = kwargs.pop('log_stack_trace', None) msg = self._log_message(msg, log_stack_trace) self._logging.warning(msg, *args, **kwargs) def info(self, msg, *args, **kwargs): if len(args) == 1 and isinstance(args[0], dict) and not self.log_context.get('enable_pii'): args = (scrub_pii(args[0]),) log_stack_trace = kwargs.pop('log_stack_trace', None) msg = self._log_message(msg, log_stack_trace) self._logging.info(msg, *args, **kwargs) def debug(self, msg, *args, **kwargs): if len(args) == 1 and isinstance(args[0], dict) and not self.log_context.get('enable_pii'): args = (scrub_pii(args[0]),) log_stack_trace = kwargs.pop('log_stack_trace', None) msg = self._log_message(msg, log_stack_trace) self._logging.debug(msg, *args, **kwargs) def exception(self, msg, *args, **kwargs): if len(args) == 1 and isinstance(args[0], dict) and not self.log_context.get('enable_pii'): args = (scrub_pii(args[0]),) msg = self._log_message(msg) self._logging.exception(msg, *args, **kwargs) def scrub_pii(arg_dict, padding="..."): """ The input is a dict with semantic keys, and the output will be a dict with PII values replaced by padding. """ pii = set([ # Personally Identifiable Information "subject", "upn", # i.e. user name "given_name", "family_name", "email", "oid", # Object ID "userid", # Used in ADAL Python token cache "login_hint", "home_oid", "access_token", "refresh_token", "id_token", "token_response", # The following are actually Organizationally Identifiable Info "tenant_id", "authority", # which typically contains tenant_id "client_id", "_clientid", # This is the key name ADAL uses in cache query "redirect_uri", # Unintuitively, the following can contain PII "user_realm_url", # e.g. https://login.microsoftonline.com/common/UserRealm/{username} ]) return {k: padding if k.lower() in pii else arg_dict[k] for k in arg_dict} azure-activedirectory-library-for-python-1.2.7/adal/mex.py000066400000000000000000000267671403263465100237040ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse # pylint: disable=import-error try: from xml.etree import cElementTree as ET except ImportError: from xml.etree import ElementTree as ET import requests from . import log from . import util from . import xmlutil from .constants import XmlNamespaces, WSTrustVersion from .adal_error import AdalError TRANSPORT_BINDING_XPATH = 'wsp:ExactlyOne/wsp:All/sp:TransportBinding' TRANSPORT_BINDING_2005_XPATH = 'wsp:ExactlyOne/wsp:All/sp2005:TransportBinding' #pylint: disable=invalid-name SOAP_ACTION_XPATH = 'wsdl:operation/soap12:operation' RST_SOAP_ACTION_13 = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue' RST_SOAP_ACTION_2005 = 'http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue' #pylint: disable=invalid-name SOAP_TRANSPORT_XPATH = 'soap12:binding' SOAP_HTTP_TRANSPORT_VALUE = 'http://schemas.xmlsoap.org/soap/http' PORT_XPATH = 'wsdl:service/wsdl:port' ADDRESS_XPATH = 'wsa10:EndpointReference/wsa10:Address' def _url_is_secure(endpoint_url): parsed = urlparse(endpoint_url) return parsed.scheme == 'https' class Mex(object): def __init__(self, call_context, url): self._log = log.Logger("MEX", call_context.get('log_context')) self._call_context = call_context self._url = url self._dom = None self._parents = None self._mex_doc = None self.username_password_policy = {} self._log.debug("Mex created with url: %(mex_url)s", {"mex_url": self._url}) def discover(self): options = util.create_request_options(self, {'headers': {'Content-Type': 'application/soap+xml'}}) try: operation = "Mex Get" resp = requests.get(self._url, headers=options['headers'], verify=self._call_context.get('verify_ssl', None), proxies=self._call_context.get('proxies', None)) util.log_return_correlation_id(self._log, operation, resp) except Exception: self._log.exception( "%(operation)s request failed", {"operation": operation}) raise if resp.status_code == 429: resp.raise_for_status() # Will raise requests.exceptions.HTTPError if not util.is_http_success(resp.status_code): return_error_string = u"{} request returned http error: {}".format(operation, resp.status_code) error_response = "" if resp.text: return_error_string = u"{} and server response: {}".format(return_error_string, resp.text) try: error_response = resp.json() except ValueError: pass raise AdalError(return_error_string, error_response) else: try: self._mex_doc = resp.text #options = {'errorHandler':self._log.error} self._dom = ET.fromstring(self._mex_doc) self._parents = {c:p for p in self._dom.iter() for c in p} self._parse() except Exception: self._log.info('Failed to parse mex response in to DOM') raise def _check_policy(self, policy_node): policy_id = policy_node.attrib["{{{}}}Id".format(XmlNamespaces.namespaces['wsu'])] # Try with Transport Binding XPath transport_binding_nodes = xmlutil.xpath_find(policy_node, TRANSPORT_BINDING_XPATH) # If unsuccessful, try again with 2005 XPath if not transport_binding_nodes: transport_binding_nodes = xmlutil.xpath_find(policy_node, TRANSPORT_BINDING_2005_XPATH) # If we did not find any binding, this is potentially bad. if not transport_binding_nodes: self._log.debug( "Potential policy did not match required transport binding: %(policy_id)s", {"policy_id": policy_id}) else: self._log.debug("Found matching policy id: %(policy_id)s", {"policy_id": policy_id}) return policy_id def _select_username_password_polices(self, xpath): policies = {} username_token_nodes = xmlutil.xpath_find(self._dom, xpath) if not username_token_nodes: self._log.warn("No username token policy nodes found.") return for node in username_token_nodes: policy_node = self._parents[self._parents[self._parents[self._parents[self._parents[self._parents[self._parents[node]]]]]]] policy_id = self._check_policy(policy_node) if policy_id: id_ref = '#' + policy_id policies[id_ref] = {policy_id:id_ref} return policies if policies else None def _check_soap_action_and_transport(self, binding_node): soap_action = "" soap_transport = "" name = binding_node.get('name') soap_transport_attributes = "" soap_action_attributes = xmlutil.xpath_find(binding_node, SOAP_ACTION_XPATH)[0].attrib['soapAction'] if soap_action_attributes: soap_action = soap_action_attributes soap_transport_attributes = xmlutil.xpath_find(binding_node, SOAP_TRANSPORT_XPATH)[0].attrib['transport'] if soap_transport_attributes: soap_transport = soap_transport_attributes if soap_transport == SOAP_HTTP_TRANSPORT_VALUE: if soap_action == RST_SOAP_ACTION_13: self._log.debug( 'found binding matching Action and Transport: %(binding_node)s', {"binding_node": name}) return WSTrustVersion.WSTRUST13 elif soap_action == RST_SOAP_ACTION_2005: self._log.debug( 'found binding matching Action and Transport: %(binding_node)s', {"binding_node": name}) return WSTrustVersion.WSTRUST2005 self._log.debug( 'binding node did not match soap Action or Transport: %(binding_node)s', {"binding_node": name}) return WSTrustVersion.UNDEFINED def _get_matching_bindings(self, policies): bindings = {} binding_policy_ref_nodes = xmlutil.xpath_find(self._dom, 'wsdl:binding/wsp:PolicyReference') for node in binding_policy_ref_nodes: uri = node.get('URI') policy = policies.get(uri) if policy: binding_node = self._parents[node] binding_name = binding_node.get('name') version = self._check_soap_action_and_transport(binding_node) if version != WSTrustVersion.UNDEFINED: bindings[binding_name] = { 'url': uri, 'version': version } return bindings if bindings else None def _get_ports_for_policy_bindings(self, bindings, policies): port_nodes = xmlutil.xpath_find(self._dom, PORT_XPATH) if not port_nodes: self._log.warn("No ports found") for node in port_nodes: binding_id = node.get('binding') binding_id = binding_id.split(':')[-1] trust_policy = bindings.get(binding_id) if trust_policy: binding_policy = policies.get(trust_policy.get('url')) if binding_policy and not binding_policy.get('url', None): binding_policy['version'] = trust_policy['version'] address_node = node.find(ADDRESS_XPATH, XmlNamespaces.namespaces) if address_node is None: raise AdalError("No address nodes on port") address = xmlutil.find_element_text(address_node) if _url_is_secure(address): binding_policy['url'] = address else: self._log.warn( "Skipping insecure endpoint: %(mex_endpoint)s", {"mex_endpoint": address}) def _select_single_matching_policy(self, policies): matching_policies = [p for p in policies.values() if p.get('url')] if not matching_policies: self._log.warn("No policies found with a url.") return wstrust13_policy = None wstrust2005_policy = None for policy in matching_policies: version = policy.get('version', None) if version == WSTrustVersion.WSTRUST13: wstrust13_policy = policy elif version == WSTrustVersion.WSTRUST2005: wstrust2005_policy = policy if wstrust13_policy is None and wstrust2005_policy is None: self._log.warn('No policies found for either wstrust13 or wstrust2005') self.username_password_policy = wstrust13_policy or wstrust2005_policy def _parse(self): policies = self._select_username_password_polices( 'wsp:Policy/wsp:ExactlyOne/wsp:All/sp:SignedEncryptedSupportingTokens/wsp:Policy/sp:UsernameToken/wsp:Policy/sp:WssUsernameToken10') xpath2005 = 'wsp:Policy/wsp:ExactlyOne/wsp:All/sp2005:SignedSupportingTokens/wsp:Policy/sp2005:UsernameToken/wsp:Policy/sp2005:WssUsernameToken10' if policies: policies2005 = self._select_username_password_polices(xpath2005) if policies2005: policies.update(policies2005) else: policies = self._select_username_password_polices(xpath2005) if not policies: raise AdalError("No matching policies.") bindings = self._get_matching_bindings(policies) if not bindings: raise AdalError("No matching bindings.") self._get_ports_for_policy_bindings(bindings, policies) self._select_single_matching_policy(policies) if not self._url: raise AdalError("No ws-trust endpoints match requirements.") azure-activedirectory-library-for-python-1.2.7/adal/oauth2_client.py000066400000000000000000000361441403263465100256410ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ from datetime import datetime, timedelta import math import re import json import time import uuid try: from urllib.parse import urlencode, urlparse except ImportError: from urllib import urlencode # pylint: disable=no-name-in-module from urlparse import urlparse # pylint: disable=import-error,ungrouped-imports import requests from . import log from . import util from .constants import OAuth2, TokenResponseFields, IdTokenFields from .adal_error import AdalError TOKEN_RESPONSE_MAP = { OAuth2.ResponseParameters.TOKEN_TYPE : TokenResponseFields.TOKEN_TYPE, OAuth2.ResponseParameters.ACCESS_TOKEN : TokenResponseFields.ACCESS_TOKEN, OAuth2.ResponseParameters.REFRESH_TOKEN : TokenResponseFields.REFRESH_TOKEN, OAuth2.ResponseParameters.CREATED_ON : TokenResponseFields.CREATED_ON, OAuth2.ResponseParameters.EXPIRES_ON : TokenResponseFields.EXPIRES_ON, OAuth2.ResponseParameters.EXPIRES_IN : TokenResponseFields.EXPIRES_IN, OAuth2.ResponseParameters.RESOURCE : TokenResponseFields.RESOURCE, OAuth2.ResponseParameters.ERROR : TokenResponseFields.ERROR, OAuth2.ResponseParameters.ERROR_DESCRIPTION : TokenResponseFields.ERROR_DESCRIPTION, } _REQ_OPTION = {'headers' : {'content-type': 'application/x-www-form-urlencoded'}} _ERROR_TEMPLATE = u"{} request returned http error: {}" def map_fields(in_obj, map_to): return dict((map_to[k], v) for k, v in in_obj.items() if k in map_to) def _get_user_id(id_token): user_id = None is_displayable = False if id_token.get('upn'): user_id = id_token['upn'] is_displayable = True elif id_token.get('email'): user_id = id_token['email'] is_displayable = True elif id_token.get('sub'): user_id = id_token['sub'] if not user_id: user_id = str(uuid.uuid4()) user_id_vals = {} user_id_vals[IdTokenFields.USER_ID] = user_id if is_displayable: user_id_vals[IdTokenFields.IS_USER_ID_DISPLAYABLE] = True return user_id_vals def _extract_token_values(id_token): extracted_values = {} extracted_values = map_fields(id_token, OAuth2.IdTokenMap) extracted_values.update(_get_user_id(id_token)) return extracted_values class OAuth2Client(object): def __init__(self, call_context, authority): self._token_endpoint = authority.token_endpoint self._device_code_endpoint = authority.device_code_endpoint self._log = log.Logger("OAuth2Client", call_context['log_context']) self._call_context = call_context self._cancel_polling_request = False def _create_token_url(self): parameters = {} if self._call_context.get('api_version'): parameters[OAuth2.Parameters.AAD_API_VERSION] = self._call_context[ 'api_version'] return urlparse('{}?{}'.format(self._token_endpoint, urlencode(parameters))) def _create_device_code_url(self): parameters = {} parameters[OAuth2.Parameters.AAD_API_VERSION] = '1.0' return urlparse('{}?{}'.format(self._device_code_endpoint, urlencode(parameters))) def _parse_optional_ints(self, obj, keys): for key in keys: try: obj[key] = int(obj[key]) except ValueError: self._log.exception("%(key)s could not be parsed as an int", {"key": key}) raise except KeyError: # if the key isn't present we can just continue pass def _parse_id_token(self, encoded_token): cracked_token = self._open_jwt(encoded_token) if not cracked_token: return try: b64_id_token = cracked_token['JWSPayload'] b64_decoded = util.base64_urlsafe_decode(b64_id_token) if not b64_decoded: self._log.warn('The returned id_token could not be base64 url safe decoded.') return id_token = json.loads(b64_decoded.decode('utf-8')) except ValueError: self._log.exception( "The returned id_token could not be decoded: %(id_token)s", {"id_token": encoded_token}) raise return _extract_token_values(id_token) def _open_jwt(self, jwt_token): id_token_parts_reg = r"^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$" matches = re.search(id_token_parts_reg, jwt_token) if not matches or len(matches.groups()) < 3: self._log.warn('The token was not parsable.') return {} return { 'header': matches.group(1), 'JWSPayload': matches.group(2), 'JWSSig': matches.group(3) } def _validate_token_response(self, body): try: wire_response = json.loads(body) except ValueError: self._log.exception( 'The token response from the server is unparseable as JSON: %(token_response)s', {"token_response": body}) raise int_keys = [ OAuth2.ResponseParameters.EXPIRES_ON, OAuth2.ResponseParameters.EXPIRES_IN, OAuth2.ResponseParameters.CREATED_ON ] self._parse_optional_ints(wire_response, int_keys) expires_in = wire_response.get(OAuth2.ResponseParameters.EXPIRES_IN) if expires_in: now = datetime.now() soon = timedelta(seconds=expires_in) wire_response[OAuth2.ResponseParameters.EXPIRES_ON] = str(now + soon) created_on = wire_response.get(OAuth2.ResponseParameters.CREATED_ON) if created_on: temp_date = datetime.fromtimestamp(created_on) 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', wire_response) if not wire_response.get(OAuth2.ResponseParameters.ACCESS_TOKEN): raise AdalError('wire_response is missing access_token', wire_response) token_response = map_fields(wire_response, TOKEN_RESPONSE_MAP) if wire_response.get(OAuth2.ResponseParameters.ID_TOKEN): id_token = self._parse_id_token(wire_response[OAuth2.ResponseParameters.ID_TOKEN]) if id_token: token_response.update(id_token) return token_response def _validate_device_code_response(self, body): try: wire_response = json.loads(body) except ValueError: self._log.info('The device code response returned from the server is unparseable as JSON:') raise int_keys = [ OAuth2.DeviceCodeResponseParameters.EXPIRES_IN, OAuth2.DeviceCodeResponseParameters.INTERVAL ] self._parse_optional_ints(wire_response, int_keys) if not wire_response.get(OAuth2.DeviceCodeResponseParameters.EXPIRES_IN): raise AdalError('wire_response is missing expires_in', wire_response) if not wire_response.get(OAuth2.DeviceCodeResponseParameters.DEVICE_CODE): raise AdalError('wire_response is missing device_code', wire_response) if not wire_response.get(OAuth2.DeviceCodeResponseParameters.USER_CODE): raise AdalError('wire_response is missing user_code', wire_response) #skip field naming tweak, becasue names from wire are python style already return wire_response def _handle_get_token_response(self, body): try: return self._validate_token_response(body) except Exception: self._log.exception( "Error validating get token response: %(token_response)s", {"token_response": body}) raise def _handle_get_device_code_response(self, body): try: return self._validate_device_code_response(body) except Exception: self._log.exception( "Error validating get user code response: %(token_response)s", {"token_response": body}) raise def get_token(self, oauth_parameters): token_url = self._create_token_url() url_encoded_token_request = urlencode(oauth_parameters) post_options = util.create_request_options(self, _REQ_OPTION) operation = "Get Token" try: resp = requests.post(token_url.geturl(), data=url_encoded_token_request, headers=post_options['headers'], verify=self._call_context.get('verify_ssl', None), proxies=self._call_context.get('proxies', None), timeout=self._call_context.get('timeout', None)) util.log_return_correlation_id(self._log, operation, resp) except Exception: self._log.exception("%(operation)s request failed", {"operation": operation}) raise if util.is_http_success(resp.status_code): return self._handle_get_token_response(resp.text) else: if resp.status_code == 429: resp.raise_for_status() # Will raise requests.exceptions.HTTPError return_error_string = _ERROR_TEMPLATE.format(operation, resp.status_code) error_response = "" if resp.text: return_error_string = u"{} and server response: {}".format(return_error_string, resp.text) try: error_response = resp.json() except ValueError: pass raise AdalError(return_error_string, error_response) def get_user_code_info(self, oauth_parameters): device_code_url = self._create_device_code_url() url_encoded_code_request = urlencode(oauth_parameters) post_options = util.create_request_options(self, _REQ_OPTION) operation = "Get Device Code" try: resp = requests.post(device_code_url.geturl(), data=url_encoded_code_request, headers=post_options['headers'], verify=self._call_context.get('verify_ssl', None), proxies=self._call_context.get('proxies', None), timeout=self._call_context.get('timeout', None)) util.log_return_correlation_id(self._log, operation, resp) except Exception: self._log.exception("%(operation)s request failed", {"operation": operation}) raise if util.is_http_success(resp.status_code): user_code_info = self._handle_get_device_code_response(resp.text) user_code_info['correlation_id'] = resp.headers.get('client-request-id') return user_code_info else: if resp.status_code == 429: resp.raise_for_status() # Will raise requests.exceptions.HTTPError return_error_string = _ERROR_TEMPLATE.format(operation, resp.status_code) error_response = "" if resp.text: return_error_string = u"{} and server response: {}".format(return_error_string, resp.text) try: error_response = resp.json() except ValueError: pass raise AdalError(return_error_string, error_response) def get_token_with_polling(self, oauth_parameters, refresh_internal, expires_in): token_url = self._create_token_url() url_encoded_code_request = urlencode(oauth_parameters) post_options = util.create_request_options(self, _REQ_OPTION) operation = "Get token with device code" max_times_for_retry = math.floor(expires_in/refresh_internal) for _ in range(int(max_times_for_retry)): if self._cancel_polling_request: raise AdalError('Polling_Request_Cancelled') resp = requests.post( token_url.geturl(), data=url_encoded_code_request, headers=post_options['headers'], proxies=self._call_context.get('proxies', None), verify=self._call_context.get('verify_ssl', None)) if resp.status_code == 429: resp.raise_for_status() # Will raise requests.exceptions.HTTPError util.log_return_correlation_id(self._log, operation, resp) wire_response = {} if not util.is_http_success(resp.status_code): # on error, the body should be json already wire_response = json.loads(resp.text) error = wire_response.get(OAuth2.DeviceCodeResponseParameters.ERROR) if error == 'authorization_pending': time.sleep(refresh_internal) continue elif error: raise AdalError('Unexpected polling state {}'.format(error), wire_response) else: try: return self._validate_token_response(resp.text) except Exception: self._log.exception( u"Error validating get token response %(access_token)s", {"access_token": resp.text}) raise raise AdalError('Timeout from "get_token_with_polling"') def cancel_polling_request(self): self._cancel_polling_request = True azure-activedirectory-library-for-python-1.2.7/adal/self_signed_jwt.py000066400000000000000000000136151403263465100262450ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 time import datetime import uuid import base64 import binascii import re import jwt from .constants import Jwt from .log import Logger from .adal_error import AdalError def _get_date_now(): return datetime.datetime.now() def _get_new_jwt_id(): return str(uuid.uuid4()) def _create_x5t_value(thumbprint): hex_val = binascii.a2b_hex(thumbprint) return base64.urlsafe_b64encode(hex_val).decode() def _sign_jwt(header, payload, certificate): try: encoded_jwt = _encode_jwt(payload, certificate, header) except Exception as exp: raise AdalError("Error:Invalid Certificate: Expected Start of Certificate to be '-----BEGIN RSA PRIVATE KEY-----'", exp) _raise_on_invalid_jwt_signature(encoded_jwt) return encoded_jwt def _encode_jwt(payload, certificate, header): encoded = jwt.encode(payload, certificate, algorithm='RS256', headers=header) try: return encoded.decode() # PyJWT 1.x returns bytes; historically we convert it to string except AttributeError: return encoded # PyJWT 2 will return string def _raise_on_invalid_jwt_signature(encoded_jwt): segments = encoded_jwt.split('.') if len(segments) < 3 or not segments[2]: raise AdalError('Failed to sign JWT. This is most likely due to an invalid certificate.') def _extract_certs(public_cert_content): # Parses raw public certificate file contents and returns a list of strings # Usage: headers = {"x5c": extract_certs(open("my_cert.pem").read())} public_certificates = re.findall( r'-----BEGIN CERTIFICATE-----(?P[^-]+)-----END CERTIFICATE-----', public_cert_content, re.I) if public_certificates: return [cert.strip() for cert in public_certificates] # The public cert tags are not found in the input, # let's make best effort to exclude a private key pem file. if "PRIVATE KEY" in public_cert_content: raise ValueError( "We expect your public key but detect a private key instead") return [public_cert_content.strip()] class SelfSignedJwt(object): NumCharIn128BitHexString = 128/8*2 numCharIn160BitHexString = 160/8*2 ThumbprintRegEx = r"^[a-f\d]*$" def __init__(self, call_context, authority, client_id): self._log = Logger('SelfSignedJwt', call_context['log_context']) self._call_context = call_context self._authortiy = authority self._token_endpoint = authority.token_endpoint self._client_id = client_id def _create_header(self, thumbprint, public_certificate): x5t = _create_x5t_value(thumbprint) header = {'typ':'JWT', 'alg':'RS256', 'x5t':x5t} if public_certificate: header['x5c'] = _extract_certs(public_certificate) self._log.debug("Creating self signed JWT header. x5t: %(x5t)s, x5c: %(x5c)s", {"x5t": x5t, "x5c": public_certificate}) return header def _create_payload(self): now = _get_date_now() minutes = datetime.timedelta(0, 0, 0, 0, Jwt.SELF_SIGNED_JWT_LIFETIME) expires = now + minutes self._log.debug( 'Creating self signed JWT payload. Expires: %(expires)s NotBefore: %(nbf)s', {"expires": expires, "nbf": now}) jwt_payload = {} jwt_payload[Jwt.AUDIENCE] = self._token_endpoint jwt_payload[Jwt.ISSUER] = self._client_id jwt_payload[Jwt.SUBJECT] = self._client_id jwt_payload[Jwt.NOT_BEFORE] = int(time.mktime(now.timetuple())) jwt_payload[Jwt.EXPIRES_ON] = int(time.mktime(expires.timetuple())) jwt_payload[Jwt.JWT_ID] = _get_new_jwt_id() return jwt_payload def _raise_on_invalid_thumbprint(self, thumbprint): thumbprint_sizes = [self.NumCharIn128BitHexString, self.numCharIn160BitHexString] size_ok = len(thumbprint) in thumbprint_sizes if not size_ok or not re.search(self.ThumbprintRegEx, thumbprint): raise AdalError("The thumbprint does not match a known format") def _reduce_thumbprint(self, thumbprint): canonical = thumbprint.lower().replace(' ', '').replace(':', '') self._raise_on_invalid_thumbprint(canonical) return canonical def create(self, certificate, thumbprint, public_certificate): thumbprint = self._reduce_thumbprint(thumbprint) header = self._create_header(thumbprint, public_certificate) payload = self._create_payload() return _sign_jwt(header, payload, certificate) azure-activedirectory-library-for-python-1.2.7/adal/token_cache.py000066400000000000000000000112361403263465100253370ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 json import threading from .constants import TokenResponseFields def _string_cmp(str1, str2): '''Case insensitive comparison. Return true if both are None''' str1 = str1 if str1 is not None else '' str2 = str2 if str2 is not None else '' return str1.lower() == str2.lower() class TokenCacheKey(object): # pylint: disable=too-few-public-methods def __init__(self, authority, resource, client_id, user_id): self.authority = authority self.resource = resource self.client_id = client_id self.user_id = user_id def __hash__(self): return hash((self.authority, self.resource, self.client_id, self.user_id)) def __eq__(self, other): return _string_cmp(self.authority, other.authority) and \ _string_cmp(self.resource, other.resource) and \ _string_cmp(self.client_id, other.client_id) and \ _string_cmp(self.user_id, other.user_id) def __ne__(self, other): return not self == other # pylint: disable=protected-access def _get_cache_key(entry): return TokenCacheKey( entry.get(TokenResponseFields._AUTHORITY), entry.get(TokenResponseFields.RESOURCE), entry.get(TokenResponseFields._CLIENT_ID), entry.get(TokenResponseFields.USER_ID)) class TokenCache(object): def __init__(self, state=None): self._cache = {} self._lock = threading.RLock() if state: self.deserialize(state) self.has_state_changed = False def find(self, query): with self._lock: return self._query_cache( query.get(TokenResponseFields.IS_MRRT), query.get(TokenResponseFields.USER_ID), query.get(TokenResponseFields._CLIENT_ID)) def remove(self, entries): with self._lock: for e in entries: key = _get_cache_key(e) removed = self._cache.pop(key, None) if removed is not None: self.has_state_changed = True def add(self, entries): with self._lock: for e in entries: key = _get_cache_key(e) self._cache[key] = e self.has_state_changed = True def serialize(self): with self._lock: return json.dumps(list(self._cache.values())) def deserialize(self, state): with self._lock: self._cache.clear() if state: tokens = json.loads(state) for t in tokens: key = _get_cache_key(t) self._cache[key] = t def read_items(self): '''output list of tuples in (key, authentication-result)''' with self._lock: return self._cache.items() def _query_cache(self, is_mrrt, user_id, client_id): matches = [] for k in self._cache: v = self._cache[k] #None value will be taken as wildcard match #pylint: disable=too-many-boolean-expressions if ((is_mrrt is None or is_mrrt == v.get(TokenResponseFields.IS_MRRT)) and (user_id is None or _string_cmp(user_id, v.get(TokenResponseFields.USER_ID))) and (client_id is None or _string_cmp(client_id, v.get(TokenResponseFields._CLIENT_ID)))): matches.append(v) return matches azure-activedirectory-library-for-python-1.2.7/adal/token_request.py000066400000000000000000000432421403263465100257660ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ from base64 import b64encode from . import constants from . import log from . import mex from . import oauth2_client from . import self_signed_jwt from . import user_realm from . import wstrust_request from .adal_error import AdalError from .cache_driver import CacheDriver from .constants import WSTrustVersion OAUTH2_PARAMETERS = constants.OAuth2.Parameters TOKEN_RESPONSE_FIELDS = constants.TokenResponseFields OAUTH2_GRANT_TYPE = constants.OAuth2.GrantType OAUTH2_SCOPE = constants.OAuth2.Scope OAUTH2_DEVICE_CODE_RESPONSE_PARAMETERS = constants.OAuth2.DeviceCodeResponseParameters SAML = constants.Saml ACCOUNT_TYPE = constants.UserRealm.account_type USER_ID = constants.TokenResponseFields.USER_ID _CLIENT_ID = constants.TokenResponseFields._CLIENT_ID #pylint: disable=protected-access def add_parameter_if_available(parameters, key, value): if value: parameters[key] = value def _get_saml_grant_type(wstrust_response): token_type = wstrust_response.token_type if token_type == SAML.TokenTypeV1 or token_type == SAML.OasisWssSaml11TokenProfile11: return OAUTH2_GRANT_TYPE.SAML1 elif token_type == SAML.TokenTypeV2 or token_type == SAML.OasisWssSaml2TokenProfile2: return OAUTH2_GRANT_TYPE.SAML2 else: raise AdalError("RSTR returned unknown token type: {}".format(token_type)) class TokenRequest(object): def __init__(self, call_context, authentication_context, client_id, resource, redirect_uri=None): self._log = log.Logger("TokenRequest", call_context['log_context']) self._call_context = call_context self._authentication_context = authentication_context self._resource = resource self._client_id = client_id self._redirect_uri = redirect_uri self._cache_driver = None # should be set at the beginning of get_token # functions that have a user_id self._user_id = None self._user_realm = None # should be set when acquire token using device flow self._polling_client = None def _create_user_realm_request(self, username): return user_realm.UserRealm(self._call_context, username, self._authentication_context.authority.url) def _create_mex(self, mex_endpoint): return mex.Mex(self._call_context, mex_endpoint) def _create_wstrust_request(self, wstrust_endpoint, applies_to, wstrust_endpoint_version): return wstrust_request.WSTrustRequest(self._call_context, wstrust_endpoint, applies_to, wstrust_endpoint_version) def _create_oauth2_client(self): return oauth2_client.OAuth2Client(self._call_context, self._authentication_context.authority) def _create_self_signed_jwt(self): return self_signed_jwt.SelfSignedJwt(self._call_context, self._authentication_context.authority, self._client_id) def _oauth_get_token(self, oauth_parameters): client = self._create_oauth2_client() return client.get_token(oauth_parameters) def _create_cache_driver(self): return CacheDriver( self._call_context, self._authentication_context.authority.url, self._resource, self._client_id, self._authentication_context.cache, self._get_token_with_token_response ) def _find_token_from_cache(self): self._cache_driver = self._create_cache_driver() cache_query = self._create_cache_query() return self._cache_driver.find(cache_query) def _add_token_into_cache(self, token): cache_driver = self._create_cache_driver() self._log.debug('Storing retrieved token into cache') cache_driver.add(token) def _get_token_with_token_response(self, entry, resource): self._log.debug("called to refresh a token from the cache") refresh_token = entry[TOKEN_RESPONSE_FIELDS.REFRESH_TOKEN] return self._get_token_with_refresh_token(refresh_token, resource, None) def _create_cache_query(self): query = {_CLIENT_ID : self._client_id} if self._user_id: query[USER_ID] = self._user_id else: self._log.debug("No user_id passed for cache query") return query def _create_oauth_parameters(self, grant_type): oauth_parameters = {} oauth_parameters[OAUTH2_PARAMETERS.GRANT_TYPE] = grant_type if (OAUTH2_GRANT_TYPE.AUTHORIZATION_CODE != grant_type and OAUTH2_GRANT_TYPE.CLIENT_CREDENTIALS != grant_type and OAUTH2_GRANT_TYPE.REFRESH_TOKEN != grant_type and OAUTH2_GRANT_TYPE.DEVICE_CODE != grant_type): oauth_parameters[OAUTH2_PARAMETERS.SCOPE] = OAUTH2_SCOPE.OPENID add_parameter_if_available(oauth_parameters, OAUTH2_PARAMETERS.CLIENT_ID, self._client_id) add_parameter_if_available(oauth_parameters, OAUTH2_PARAMETERS.RESOURCE, self._resource) add_parameter_if_available(oauth_parameters, OAUTH2_PARAMETERS.REDIRECT_URI, self._redirect_uri) return oauth_parameters def _get_token_username_password_managed(self, username, password): self._log.debug('Acquiring token with username password for managed user') oauth_parameters = self._create_oauth_parameters(OAUTH2_GRANT_TYPE.PASSWORD) oauth_parameters[OAUTH2_PARAMETERS.PASSWORD] = password oauth_parameters[OAUTH2_PARAMETERS.USERNAME] = username return self._oauth_get_token(oauth_parameters) def _perform_wstrust_assertion_oauth_exchange(self, wstrust_response): self._log.debug("Performing OAuth assertion grant type exchange.") oauth_parameters = {} grant_type = _get_saml_grant_type(wstrust_response) token_bytes = wstrust_response.token assertion = b64encode(token_bytes) oauth_parameters = self._create_oauth_parameters(grant_type) oauth_parameters[OAUTH2_PARAMETERS.ASSERTION] = assertion return self._oauth_get_token(oauth_parameters) def _perform_wstrust_exchange(self, wstrust_endpoint, wstrust_endpoint_version, cloud_audience_urn, username, password): wstrust = self._create_wstrust_request(wstrust_endpoint, cloud_audience_urn, wstrust_endpoint_version) result = wstrust.acquire_token(username, password) if not result.token: err_template = "Unsuccessful RSTR.\n\terror code: {}\n\tfaultMessage: {}" error_msg = err_template.format(result.error_code, result.fault_message) self._log.info(error_msg) raise AdalError(error_msg) return result def _perform_username_password_for_access_token_exchange(self, wstrust_endpoint, wstrust_endpoint_version, cloud_audience_urn, username, password): wstrust_response = self._perform_wstrust_exchange(wstrust_endpoint, wstrust_endpoint_version, cloud_audience_urn, username, password) return self._perform_wstrust_assertion_oauth_exchange(wstrust_response) def _get_token_username_password_federated(self, username, password): self._log.debug("Acquiring token with username password for federated user") cloud_audience_urn = self._user_realm.cloud_audience_urn if not self._user_realm.federation_metadata_url: self._log.warn("Unable to retrieve federationMetadataUrl from AAD. " "Attempting fallback to AAD supplied endpoint.") if not self._user_realm.federation_active_auth_url: raise AdalError('AAD did not return a WSTrust endpoint. Unable to proceed.') wstrust_version = TokenRequest._parse_wstrust_version_from_federation_active_authurl( self._user_realm.federation_active_auth_url) self._log.debug( 'wstrust endpoint version is: %(wstrust_version)s', {"wstrust_version": wstrust_version}) return self._perform_username_password_for_access_token_exchange( self._user_realm.federation_active_auth_url, wstrust_version, cloud_audience_urn, username, password) else: mex_endpoint = self._user_realm.federation_metadata_url self._log.debug( "Attempting mex at: %(mex_endpoint)s", {"mex_endpoint": mex_endpoint}) mex_instance = self._create_mex(mex_endpoint) wstrust_version = WSTrustVersion.UNDEFINED try: mex_instance.discover() wstrust_endpoint = mex_instance.username_password_policy['url'] wstrust_version = mex_instance.username_password_policy['version'] except Exception: #pylint: disable=broad-except self._log.warn( "MEX exchange failed for %(mex_endpoint)s. " "Attempting fallback to AAD supplied endpoint.", {"mex_endpoint": mex_endpoint}) wstrust_endpoint = self._user_realm.federation_active_auth_url wstrust_version = TokenRequest._parse_wstrust_version_from_federation_active_authurl( self._user_realm.federation_active_auth_url) if not wstrust_endpoint: raise AdalError('AAD did not return a WSTrust endpoint. Unable to proceed.') return self._perform_username_password_for_access_token_exchange(wstrust_endpoint, wstrust_version, cloud_audience_urn, username, password) @staticmethod def _parse_wstrust_version_from_federation_active_authurl(federation_active_authurl): if '/trust/2005/usernamemixed' in federation_active_authurl: return WSTrustVersion.WSTRUST2005 if '/trust/13/usernamemixed' in federation_active_authurl: return WSTrustVersion.WSTRUST13 return WSTrustVersion.UNDEFINED def get_token_with_username_password(self, username, password): self._log.debug("Acquiring token with username password.") self._user_id = username try: token = self._find_token_from_cache() if token: return token except AdalError: self._log.exception('Attempt to look for token in cache resulted in Error') if not self._authentication_context.authority.is_adfs_authority: self._user_realm = self._create_user_realm_request(username) self._user_realm.discover() try: if self._user_realm.account_type == ACCOUNT_TYPE['Managed']: token = self._get_token_username_password_managed(username, password) elif self._user_realm.account_type == ACCOUNT_TYPE['Federated']: token = self._get_token_username_password_federated(username, password) else: raise AdalError( "Server returned an unknown AccountType: {}".format(self._user_realm.account_type)) self._log.debug("Successfully retrieved token from authority.") except Exception: self._log.info("get_token_func returned with error") raise else: self._log.info('Skipping user realm discovery for ADFS authority') token = self._get_token_username_password_managed(username, password) self._cache_driver.add(token) return token def get_token_with_client_credentials(self, client_secret): self._log.debug("Getting token with client credentials.") try: token = self._find_token_from_cache() if token: return token except AdalError: self._log.exception('Attempt to look for token in cache resulted in Error') oauth_parameters = self._create_oauth_parameters(OAUTH2_GRANT_TYPE.CLIENT_CREDENTIALS) oauth_parameters[OAUTH2_PARAMETERS.CLIENT_SECRET] = client_secret token = self._oauth_get_token(oauth_parameters) self._cache_driver.add(token) return token def get_token_with_authorization_code(self, authorization_code, client_secret, code_verifier): self._log.info("Getting token with auth code.") oauth_parameters = self._create_oauth_parameters(OAUTH2_GRANT_TYPE.AUTHORIZATION_CODE) oauth_parameters[OAUTH2_PARAMETERS.CODE] = authorization_code if client_secret is not None: oauth_parameters[OAUTH2_PARAMETERS.CLIENT_SECRET] = client_secret if code_verifier is not None: oauth_parameters[OAUTH2_PARAMETERS.CODE_VERIFIER] = code_verifier token = self._oauth_get_token(oauth_parameters) self._add_token_into_cache(token) return token def _get_token_with_refresh_token(self, refresh_token, resource, client_secret): self._log.info("Getting a new token from a refresh token") oauth_parameters = self._create_oauth_parameters(OAUTH2_GRANT_TYPE.REFRESH_TOKEN) if resource: oauth_parameters[OAUTH2_PARAMETERS.RESOURCE] = resource if client_secret: oauth_parameters[OAUTH2_PARAMETERS.CLIENT_SECRET] = client_secret oauth_parameters[OAUTH2_PARAMETERS.REFRESH_TOKEN] = refresh_token return self._oauth_get_token(oauth_parameters) def get_token_with_refresh_token(self, refresh_token, client_secret): return self._get_token_with_refresh_token(refresh_token, None, client_secret) def get_token_from_cache_with_refresh(self, user_id): self._log.debug("Getting token from cache with refresh if necessary.") self._user_id = user_id return self._find_token_from_cache() def _create_jwt(self, certificate, thumbprint, public_certificate): ssj = self._create_self_signed_jwt() jwt = ssj.create(certificate, thumbprint, public_certificate) if not jwt: raise AdalError("Failed to create JWT.") return jwt def get_token_with_certificate(self, certificate, thumbprint, public_certificate): self._log.info("Getting a token via certificate.") jwt = self._create_jwt(certificate, thumbprint, public_certificate) oauth_parameters = self._create_oauth_parameters(OAUTH2_GRANT_TYPE.CLIENT_CREDENTIALS) oauth_parameters[OAUTH2_PARAMETERS.CLIENT_ASSERTION_TYPE] = OAUTH2_GRANT_TYPE.JWT_BEARER oauth_parameters[OAUTH2_PARAMETERS.CLIENT_ASSERTION] = jwt try: token = self._find_token_from_cache() if token: return token except AdalError: self._log.exception('Attempt to look for token in cache resulted in Error') return self._oauth_get_token(oauth_parameters) def get_token_with_device_code(self, user_code_info): self._log.info("Getting a token via device code") oauth_parameters = self._create_oauth_parameters(OAUTH2_GRANT_TYPE.DEVICE_CODE) oauth_parameters[OAUTH2_PARAMETERS.CODE] = user_code_info[OAUTH2_DEVICE_CODE_RESPONSE_PARAMETERS.DEVICE_CODE] interval = user_code_info[OAUTH2_DEVICE_CODE_RESPONSE_PARAMETERS.INTERVAL] expires_in = user_code_info[OAUTH2_DEVICE_CODE_RESPONSE_PARAMETERS.EXPIRES_IN] if interval <= 0: raise AdalError('invalid refresh interval') client = self._create_oauth2_client() self._polling_client = client token = client.get_token_with_polling(oauth_parameters, interval, expires_in) self._add_token_into_cache(token) return token def cancel_token_request_with_device_code(self): self._polling_client.cancel_polling_request() azure-activedirectory-library-for-python-1.2.7/adal/user_realm.py000066400000000000000000000155361403263465100252410ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 json try: from urllib.parse import quote, urlencode from urllib.parse import urlunparse except ImportError: from urllib import quote, urlencode #pylint: disable=no-name-in-module from urlparse import urlunparse #pylint: disable=import-error import requests from . import constants from . import log from . import util from .adal_error import AdalError USER_REALM_PATH_TEMPLATE = 'common/UserRealm/' ACCOUNT_TYPE = constants.UserRealm.account_type FEDERATION_PROTOCOL_TYPE = constants.UserRealm.federation_protocol_type class UserRealm(object): def __init__(self, call_context, user_principle, authority_url): self._log = log.Logger("UserRealm", call_context['log_context']) self._call_context = call_context self.api_version = '1.0' self.federation_protocol = None self.account_type = None self.federation_metadata_url = None self.federation_active_auth_url = None self.cloud_audience_urn = None self._user_principle = user_principle self._authority_url = authority_url def _get_user_realm_url(self): url_components = list(util.copy_url(self._authority_url)) url_encoded_user = quote(self._user_principle, safe='~()*!.\'') url_components[2] = '/' + USER_REALM_PATH_TEMPLATE.replace('', url_encoded_user) user_realm_query = {'api-version':self.api_version} url_components[4] = urlencode(user_realm_query) return util.copy_url(urlunparse(url_components)) @staticmethod def _validate_constant_value(value_dic, value, case_sensitive=False): if not value: return False if not case_sensitive: value = value.lower() return value if value in value_dic.values() else False @staticmethod def _validate_account_type(account_type): return UserRealm._validate_constant_value(ACCOUNT_TYPE, account_type) @staticmethod def _validate_federation_protocol(protocol): return UserRealm._validate_constant_value(FEDERATION_PROTOCOL_TYPE, protocol) def _log_parsed_response(self): self._log.debug( 'UserRealm response:\n' ' AccountType: %(account_type)s\n' ' FederationProtocol: %(federation_protocol)s\n' ' FederationMetatdataUrl: %(federation_metadata_url)s\n' ' FederationActiveAuthUrl: %(federation_active_auth_url)s', { "account_type": self.account_type, "federation_protocol": self.federation_protocol, "federation_metadata_url": self.federation_metadata_url, "federation_active_auth_url": self.federation_active_auth_url, }) def _parse_discovery_response(self, body): self._log.debug("Discovery response:\n %(discovery_response)s", {"discovery_response": body}) try: response = json.loads(body) except ValueError: self._log.info( "Parsing realm discovery response JSON failed for body: %(body)s", {"body": body}) raise account_type = UserRealm._validate_account_type(response['account_type']) if not account_type: raise AdalError('Cannot parse account_type: {}'.format(account_type)) self.account_type = account_type if self.account_type == ACCOUNT_TYPE['Federated']: protocol = UserRealm._validate_federation_protocol(response['federation_protocol']) if not protocol: raise AdalError('Cannot parse federation protocol: {}'.format(protocol)) self.federation_protocol = protocol self.federation_metadata_url = response['federation_metadata_url'] self.federation_active_auth_url = response['federation_active_auth_url'] self.cloud_audience_urn = response.get('cloud_audience_urn', "urn:federation:MicrosoftOnline") self._log_parsed_response() def discover(self): options = util.create_request_options(self, {'headers': {'Accept':'application/json'}}) user_realm_url = self._get_user_realm_url() self._log.debug("Performing user realm discovery at: %(user_realm_url)s", {"user_realm_url": user_realm_url.geturl()}) operation = 'User Realm Discovery' resp = requests.get(user_realm_url.geturl(), headers=options['headers'], proxies=self._call_context.get('proxies', None), verify=self._call_context.get('verify_ssl', None)) util.log_return_correlation_id(self._log, operation, resp) if resp.status_code == 429: resp.raise_for_status() # Will raise requests.exceptions.HTTPError if not util.is_http_success(resp.status_code): return_error_string = u"{} request returned http error: {}".format(operation, resp.status_code) error_response = "" if resp.text: return_error_string = u"{} and server response: {}".format(return_error_string, resp.text) try: error_response = resp.json() except ValueError: pass raise AdalError(return_error_string, error_response) else: self._parse_discovery_response(resp.text) azure-activedirectory-library-for-python-1.2.7/adal/util.py000066400000000000000000000067601403263465100240570ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 sys import base64 try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse #pylint: disable=import-error import adal from .constants import AdalIdParameters def is_http_success(status_code): return status_code >= 200 and status_code < 300 def add_default_request_headers(self, options): if not options.get('headers'): options['headers'] = {} headers = options['headers'] if not headers.get('Accept-Charset'): headers['Accept-Charset'] = 'utf-8' #pylint: disable=protected-access headers['client-request-id'] = self._call_context['log_context']['correlation_id'] headers['return-client-request-id'] = 'true' headers[AdalIdParameters.SKU] = AdalIdParameters.PYTHON_SKU headers[AdalIdParameters.VERSION] = adal.__version__ headers[AdalIdParameters.OS] = sys.platform headers[AdalIdParameters.CPU] = 'x64' if sys.maxsize > 2 ** 32 else 'x86' def create_request_options(self, *options): merged_options = {} if options: for i in options: merged_options.update(i) #pylint: disable=protected-access if self._call_context.get('options') and self._call_context['options'].get('http'): merged_options.update(self._call_context['options']['http']) add_default_request_headers(self, merged_options) return merged_options def log_return_correlation_id(log, operation_message, response): if response and response.headers and response.headers.get('client-request-id'): log.debug("{} Server returned this correlation_id: {}".format( operation_message, response.headers['client-request-id'])) def copy_url(url_source): if hasattr(url_source, 'geturl'): return urlparse(url_source.geturl()) else: return urlparse(url_source) # urlsafe_b64decode requires correct padding. AAD does not include padding so # the string needs to be correctly padded before decoding. def base64_urlsafe_decode(b64string): b64string += '=' * (4 - ((len(b64string) % 4))) return base64.urlsafe_b64decode(b64string.encode('ascii')) azure-activedirectory-library-for-python-1.2.7/adal/wstrust_request.py000066400000000000000000000212001403263465100263670ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 uuid from datetime import datetime, timedelta import requests from . import log from . import util from . import wstrust_response from .adal_error import AdalError from .constants import WSTrustVersion _USERNAME_PLACEHOLDER = '{UsernamePlaceHolder}' _PASSWORD_PLACEHOLDER = '{PasswordPlaceHolder}' class WSTrustRequest(object): def __init__(self, call_context, wstrust_endpoint_url, applies_to, wstrust_endpoint_version): self._log = log.Logger('WSTrustRequest', call_context['log_context']) self._call_context = call_context self._wstrust_endpoint_url = wstrust_endpoint_url self._applies_to = applies_to self._wstrust_endpoint_version = wstrust_endpoint_version @staticmethod def _build_security_header(): time_now = datetime.utcnow() expire_time = time_now + timedelta(minutes=10) time_now_str = time_now.isoformat()[:-3] + 'Z' expire_time_str = expire_time.isoformat()[:-3] + 'Z' security_header_xml = ("" "" "" + time_now_str + "" "" + expire_time_str + "" "" "" "" + _USERNAME_PLACEHOLDER + "" "" + _PASSWORD_PLACEHOLDER + "" "" "") return security_header_xml @staticmethod def _populate_rst_username_password(template, username, password): password = WSTrustRequest._escape_password(password) return template.replace(_USERNAME_PLACEHOLDER, username).replace(_PASSWORD_PLACEHOLDER, password) @staticmethod def _escape_password(password): return password.replace('&', '&').replace('"', '"').replace("'", ''').replace('<', '<').replace('>', '>') def _build_rst(self, username, password): message_id = str(uuid.uuid4()) schema_location = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' soap_action = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue' rst_trust_namespace = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512' key_type = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer' request_type = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue' if self._wstrust_endpoint_version == WSTrustVersion.WSTRUST2005: soap_action = 'http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue' rst_trust_namespace = 'http://schemas.xmlsoap.org/ws/2005/02/trust' key_type = 'http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey' request_type = 'http://schemas.xmlsoap.org/ws/2005/02/trust/Issue' rst_template = ("".format(schema_location) + "" + "{}".format(soap_action) + "urn:uuid:{}".format(message_id) + "" + "http://www.w3.org/2005/08/addressing/anonymous" + "" + "{}".format(self._wstrust_endpoint_url) + WSTrustRequest._build_security_header() + "" + "" + "".format(rst_trust_namespace) + "" + "" + "{}".format(self._applies_to) + "" + "" + "{}".format(key_type) + "{}".format(request_type) + "" + "" + "") self._log.debug('Created RST: \n %(rst_template)s', {"rst_template": rst_template}) return WSTrustRequest._populate_rst_username_password(rst_template, username, password) def _handle_rstr(self, body): wstrust_resp = wstrust_response.WSTrustResponse(self._call_context, body, self._wstrust_endpoint_version) wstrust_resp.parse() return wstrust_resp def acquire_token(self, username, password): if self._wstrust_endpoint_version == WSTrustVersion.UNDEFINED: raise AdalError('Unsupported wstrust endpoint version. Current support version is wstrust2005 or wstrust13.') rst = self._build_rst(username, password) if self._wstrust_endpoint_version == WSTrustVersion.WSTRUST2005: soap_action = 'http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue' else: soap_action = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue' headers = {'headers': {'Content-type':'application/soap+xml; charset=utf-8', 'SOAPAction': soap_action}, 'body': rst} options = util.create_request_options(self, headers) self._log.debug("Sending RST to: %(wstrust_endpoint)s", {"wstrust_endpoint": self._wstrust_endpoint_url}) operation = "WS-Trust RST" resp = requests.post(self._wstrust_endpoint_url, headers=options['headers'], data=rst, allow_redirects=True, verify=self._call_context.get('verify_ssl', None), proxies=self._call_context.get('proxies', None), timeout=self._call_context.get('timeout', None)) util.log_return_correlation_id(self._log, operation, resp) if resp.status_code == 429: resp.raise_for_status() # Will raise requests.exceptions.HTTPError if not util.is_http_success(resp.status_code): return_error_string = u"{} request returned http error: {}".format(operation, resp.status_code) error_response = "" if resp.text: return_error_string = u"{} and server response: {}".format(return_error_string, resp.text) try: error_response = resp.json() except ValueError: pass raise AdalError(return_error_string, error_response) else: return self._handle_rstr(resp.text) azure-activedirectory-library-for-python-1.2.7/adal/wstrust_response.py000066400000000000000000000256721403263465100265560ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ try: from xml.etree import cElementTree as ET except ImportError: from xml.etree import ElementTree as ET import re from . import xmlutil from . import log from .adal_error import AdalError from .constants import WSTrustVersion # Creates a log message that contains the RSTR scrubbed of the actual SAML assertion. def scrub_rstr_log_message(response_str): # A regular expression for finding the SAML Assertion in an response_str. Used to remove the SAML # assertion when logging the response_str. assertion_regex = r'RequestedSecurityToken.*?((<.*?:Assertion.*?>).*<\/.*?Assertion>).*?' single_line_rstr, _ = re.subn(r'(\r\n|\n|\r)', '', response_str) match = re.search(assertion_regex, single_line_rstr) if not match: #No Assertion was matched so just return the response_str as is. scrubbed_rstr = single_line_rstr else: saml_assertion = match.group(1) saml_assertion_start_tag = match.group(2) scrubbed_rstr = single_line_rstr.replace( saml_assertion, saml_assertion_start_tag + 'ASSERTION CONTENTS REDACTED') return 'RSTR Response: ' + scrubbed_rstr def findall_content(xml_string, tag): """ Given a tag name without any prefix, this function returns a list of the raw content inside this tag as-is. >>> findall_content(" what ever content ", "foo") [" what ever content "] Motivation: Usually we would use XML parser to extract the data by xpath. However the ElementTree in Python will implicitly normalize the output by "hoisting" the inner inline namespaces into the outmost element. The result will be a semantically equivalent XML snippet, but not fully identical to the original one. While this effect shouldn't become a problem in all other cases, it does not seem to fully comply with Exclusive XML Canonicalization spec (https://www.w3.org/TR/xml-exc-c14n/), and void the SAML token signature. SAML signature algo needs the "XML -> C14N(XML) -> Signed(C14N(Xml))" order. The binary extention lxml is probably the canonical way to solve this (https://stackoverflow.com/questions/22959577/python-exclusive-xml-canonicalization-xml-exc-c14n) but here we use this workaround, based on Regex, to return raw content as-is. """ # \w+ is good enough for https://www.w3.org/TR/REC-xml/#NT-NameChar pattern = r"<(?:\w+:)?%(tag)s(?:[^>]*)>(.*) # # http://www.w3.org/2005/08/addressing/soap/fault # - # # 2013-07-30T00:32:21.989Z # 2013-07-30T00:37:21.989Z # # # # # # # s:Sender # # a:RequestFailed # # # # MSIS3127: The specified request failed. # # # # def _parse_error(self): error_found = False fault_node = xmlutil.xpath_find(self._dom, 's:Body/s:Fault/s:Reason/s:Text') if fault_node: self.fault_message = fault_node[0].text if self.fault_message: error_found = True # Subcode has minoccurs=0 and maxoccurs=1(default) according to the http://www.w3.org/2003/05/soap-envelope # Subcode may have another subcode as well. This is only targetting at top level subcode. # Subcode value may have different messages not always uses http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd. # text inside the value is not possible to select without prefix, so substring is necessary subnode = xmlutil.xpath_find(self._dom, 's:Body/s:Fault/s:Code/s:Subcode/s:Value') if len(subnode) > 1: raise AdalError("Found too many fault code values: {}".format(len(subnode))) if subnode: error_code = subnode[0].text self.error_code = error_code.split(':')[1] return error_found def _parse_token(self): if self._wstrust_version == WSTrustVersion.WSTRUST2005: token_type_nodes_xpath = 's:Body/t:RequestSecurityTokenResponse/t:TokenType' security_token_xpath = 't:RequestedSecurityToken' else: token_type_nodes_xpath = 's:Body/wst:RequestSecurityTokenResponseCollection/wst:RequestSecurityTokenResponse/wst:TokenType' security_token_xpath = 'wst:RequestedSecurityToken' token_type_nodes = xmlutil.xpath_find(self._dom, token_type_nodes_xpath) if not token_type_nodes: raise AdalError("No TokenType nodes found in RSTR") for node in token_type_nodes: if self.token: self._log.warn("Found more than one returned token. Using the first.") break token_type = xmlutil.find_element_text(node) if not token_type: self._log.warn("Could not find token type in RSTR token.") requested_token_node = xmlutil.xpath_find(self._parents[node], security_token_xpath) if len(requested_token_node) > 1: raise AdalError("Found too many RequestedSecurityToken nodes for token type: {}".format(token_type)) if not requested_token_node: self._log.warn( "Unable to find RequestsSecurityToken element associated with TokenType element: %(token_type)s", {"token_type": token_type}) continue # Adjust namespaces (without this they are autogenerated) so this is understood # by the receiver. Then make a string repr of the element tree node. # See also http://blog.tomhennigan.co.uk/post/46945128556/elementtree-and-xmlns ET.register_namespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion') ET.register_namespace('ds', 'http://www.w3.org/2000/09/xmldsig#') token = ET.tostring(requested_token_node[0][0]) if token is None: self._log.warn( "Unable to find token associated with TokenType element: %(token_type)s", {"token_type": token_type}) continue self.token = token self.token_type = token_type self._log.info( "Found token of type: %(token_type)s", {"token_type": self.token_type}) if self.token is None: raise AdalError("Unable to find any tokens in RSTR.") @staticmethod def _parse_token_by_re(raw_response): for rstr in findall_content(raw_response, "RequestSecurityTokenResponse"): token_types = findall_content(rstr, "TokenType") tokens = findall_content(rstr, "RequestedSecurityToken") if token_types and tokens: # Historically, we use "us-ascii" encoding, but it should be "utf-8" # https://stackoverflow.com/questions/36658000/what-is-encoding-used-for-saml-conversations return tokens[0].encode('utf-8'), token_types[0] def parse(self): if not self._response: raise AdalError("Received empty RSTR response body.") try: self._dom = ET.fromstring(self._response) except Exception as exp: raise AdalError('Failed to parse RSTR in to DOM', exp) try: self._parents = {c:p for p in self._dom.iter() for c in p} error_found = self._parse_error() if error_found: str_error_code = self.error_code or 'NONE' str_fault_message = self.fault_message or 'NONE' error_template = 'Server returned error in RSTR - ErrorCode: {} : FaultMessage: {}' raise AdalError(error_template.format(str_error_code, str_fault_message)) token_found = self._parse_token_by_re(self._response) if token_found: self.token, self.token_type = token_found else: # fallback to old logic self._parse_token() finally: self._dom = None self._parents = None azure-activedirectory-library-for-python-1.2.7/adal/xmlutil.py000066400000000000000000000053661403263465100246010ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ try: from xml.etree import cElementTree as ET except ImportError: from xml.etree import ElementTree as ET from . import constants XPATH_PATH_TEMPLATE = '*[local-name() = \'LOCAL_NAME\' and namespace-uri() = \'NAMESPACE\']' def expand_q_names(xpath): namespaces = constants.XmlNamespaces.namespaces path_parts = xpath.split('/') for index, part in enumerate(path_parts): if part.find(":") != -1: q_parts = part.split(':') if len(q_parts) != 2: raise IndexError("Unable to parse XPath string: {} with QName: {}".format(xpath, part)) expanded_path = XPATH_PATH_TEMPLATE.replace('LOCAL_NAME', q_parts[1]) expanded_path = expanded_path.replace('NAMESPACE', namespaces[q_parts[0]]) path_parts[index] = expanded_path return '/'.join(path_parts) def xpath_find(dom, xpath): return dom.findall(xpath, constants.XmlNamespaces.namespaces) def serialize_node_children(node): doc = "" for child in node.iter(): if is_element_node(child): estring = ET.tostring(child) doc += estring if isinstance(estring, str) else estring.decode() return doc if doc else None def is_element_node(node): return hasattr(node, 'tag') def find_element_text(node): for child in node.iter(): if child.text: return child.text azure-activedirectory-library-for-python-1.2.7/contributing.md000066400000000000000000000100271403263465100246470ustar00rootroot00000000000000# CONTRIBUTING Azure Active Directory SDK projects welcomes new contributors. This document will guide you through the process. ### CONTRIBUTOR LICENSE AGREEMENT Please visit [https://cla.microsoft.com/](https://cla.microsoft.com/) and sign the Contributor License Agreement. You only need to do that once. We can not look at your code until you've submitted this request. ### FORK Fork the project [on GitHub][] and check out your copy. Example for ADAL Python: ``` $ git clone git@github.com:username/azure-activedirectory-library-for-python.git $ cd azure-activedirectory-library-for-python $ git remote add upstream git@github.com:AzureAD/azure-activedirectory-library-for-python.git ``` Now decide if you want your feature or bug fix to go into the dev branch or the master branch. **All bug fixes and new features should go into the dev branch.** The master branch is effectively frozen; patches that change the SDKs protocols or API surface area or affect the run-time behavior of the SDK will be rejected. Some of our SDKs have bundled dependencies that are not part of the project proper. Any changes to files in those directories or its subdirectories should be sent to their respective projects. Do not send your patch to us, we cannot accept it. In case of doubt, open an issue in the [issue tracker][]. Especially do so if you plan to work on a major change in functionality. Nothing is more frustrating than seeing your hard work go to waste because your vision does not align with our goals for the SDK. ### BRANCH Okay, so you have decided on the proper branch. Create a feature branch and start hacking: ``` $ git checkout -b my-feature-branch ``` ### COMMIT Make sure git knows your name and email address: ``` $ git config --global user.name "J. Random User" $ git config --global user.email "j.random.user@example.com" ``` Writing good commit logs is important. A commit log should describe what changed and why. Follow these guidelines when writing one: 1. The first line should be 50 characters or less and contain a short description of the change prefixed with the name of the changed subsystem (e.g. "net: add localAddress and localPort to Socket"). 2. Keep the second line blank. 3. Wrap all other lines at 72 columns. A good commit log looks like this: ``` fix: explaining the commit in one line Body of commit message is a few lines of text, explaining things in more detail, possibly giving some background about the issue being fixed, etc etc. The body of the commit message can be several paragraphs, and please do proper word-wrap and keep columns shorter than about 72 characters or so. That way `git log` will show things nicely even when it is indented. ``` The header line should be meaningful; it is what other people see when they run `git shortlog` or `git log --oneline`. Check the output of `git log --oneline files_that_you_changed` to find out what directories your changes touch. ### REBASE Use `git rebase` (not `git merge`) to sync your work from time to time. ``` $ git fetch upstream $ git rebase upstream/v0.1 # or upstream/master ``` ### TEST Bug fixes and features should come with tests. Add your tests in the test directory. This varies by repository but often follows the same convention of /src/test. Look at other tests to see how they should be structured (license boilerplate, common includes, etc.). Make sure that all tests pass. ### PUSH ``` $ git push origin my-feature-branch ``` Go to https://github.com/username/azure-activedirectory-library-for-***.git and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. If there are comments to address, apply your changes in a separate commit and push that to your feature branch. Post a comment in the pull request afterwards; GitHub does not send out notifications when you add commits. [on GitHub]: https://github.com/AzureAD/azure-activedirectory-library-for-python [issue tracker]: https://github.com/AzureAD/azure-activedirectory-library-for-python/issues azure-activedirectory-library-for-python-1.2.7/docs/000077500000000000000000000000001403263465100225465ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/docs/Makefile000066400000000000000000000011431403263465100242050ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = ADALPython SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)azure-activedirectory-library-for-python-1.2.7/docs/make.bat000066400000000000000000000014161403263465100241550ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build set SPHINXPROJ=ADALPython if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd azure-activedirectory-library-for-python-1.2.7/docs/requirements.txt000066400000000000000000000000001403263465100260200ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/docs/source/000077500000000000000000000000001403263465100240465ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/docs/source/conf.py000066400000000000000000000136101403263465100253460ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # ADAL Python documentation build configuration file, created by # sphinx-quickstart on Tue Apr 24 14:09:40 2018. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.imgmath', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.githubpages'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'ADAL Python' copyright = '2018, Microsoft' author = 'Microsoft' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '' # The full version, including alpha/beta/rc tags. release = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { '**': [ 'relations.html', # needs 'show_related': True theme option to display 'searchbox.html', ] } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'ADALPythondoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'ADALPython.tex', 'ADAL Python Documentation', 'Microsoft', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'adalpython', 'ADAL Python Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'ADALPython', 'ADAL Python Documentation', author, 'ADALPython', 'One line description of project.', 'Miscellaneous'), ] # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} autoclass_content = 'both' azure-activedirectory-library-for-python-1.2.7/docs/source/index.rst000066400000000000000000000042601403263465100257110ustar00rootroot00000000000000.. ADAL Python documentation master file, created by sphinx-quickstart on Wed Apr 25 15:50:25 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. This file is also inspired by https://pythonhosted.org/an_example_pypi_project/sphinx.html#full-code-example .. note:: This library, ADAL for Python, will no longer receive new feature improvement. Its successor, `MSAL for Python `_, are now generally available. * If you are starting a new project, you can get started with the `MSAL Python docs `_ for details about the scenarios, usage, and relevant concepts. * If your application is using the previous ADAL Python library, you can follow this `migration guide `_ to update to MSAL Python. * Existing applications relying on ADAL Python will continue to work. Welcome to ADAL Python's documentation! ======================================= .. toctree:: :maxdepth: 2 :caption: Contents: You can find high level conceptual documentations in the project `wiki `_ and `workable samples inside the project code base `_ The documentation hosted here is for API Reference. AuthenticationContext ===================== The majority of ADAL Python functionalities are provided via the main class named `AuthenticationContext`. .. autoclass:: adal.AuthenticationContext :members: .. automethod:: __init__ TokenCache ========== One of the parameter accepted by `AuthenticationContext` is the `TokenCache`. .. autoclass:: adal.TokenCache :members: :undoc-members: If you need to subclass it, you need to refer to its source code for the detail. AdalError ========= When errors are detected by ADAL Python, it will raise this exception. .. autoclass:: adal.AdalError :members: azure-activedirectory-library-for-python-1.2.7/pylintrc000066400000000000000000000056641403263465100234200ustar00rootroot00000000000000[MASTER] ignore=.svn persistent=yes cache-size=500 load-plugins= [MESSAGES CONTROL] #enable-checker= #disable-checker=design #enable-msg-cat= #disable-msg-cat= #enable-msg= # Disabled messages: # C0321: Multiple statements on a single line # W0105: String statement has no effect # W0142: Used * or ** magic # W0404: Reimport '' # W0704: Except doesn't do anything Used when an except clause does nothing but "pass" # and there is no "else" clause. # I0011: Locally disabling (message) # R0921: Abstract class not referenced # C0111: missing-docstring # C0303: railing whitespace # C0301: Line too long disable=C0111,C0321,C0303,C0301,W0105,W0142,W0404,W0704,I0011,R0921 [REPORTS] # Available formats are text, parseable, colorized, msvs (Visual Studio) and html output-format=msvs files-output=no reports=yes # Python expression which should return a note less than 10 (10 is the highest # note).You have access to the variables errors warning, statement which # respectivly contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (R0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) #enable-report= #disable-report= [BASIC] no-docstring-rgx=__.*__ # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression which should only match correct module level names const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__)|([a-z_][a-z0-9_]*))$ # Regular expression which should only match correct class names class-rgx=[a-zA-Z0-9_]+$ # Regular expression which should only match correct function names function-rgx=[a-zA-Z_][a-zA-Z0-9_]{2,50}$ # Regular expression which should only match correct method names method-rgx=[a-z_][a-zA-Z0-9_]{2,60}$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][a-z0-9_]{1,30}$ # Regular expression which should only match correct argument names argument-rgx=[a-z_][a-z0-9_]{1,30}$ # Regular expression which should only match correct variable names variable-rgx=[a-z_][a-zA-Z0-9_]{0,40}$ # Regular expression which should only match correct list comprehension / # generator expression variable names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_,x,y # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # List of builtins function names that should not be used, separated by a comma bad-functions=filter,apply,input [DESIGN] max-args=10 max-locals=30 max-returns=6 max-branchs=18 max-statements=50 max-parents=5 max-attributes=15 min-public-methods=1 max-public-methods=20 [FORMAT] max-line-length=120 max-module-lines=1000 indent-string=' ' [SIMILARITIES] # Effectively disable similarity checking min-similarity-lines=10000 azure-activedirectory-library-for-python-1.2.7/requirements.txt000066400000000000000000000004701403263465100251030ustar00rootroot00000000000000requests==2.20.0 PyJWT==1.7.0 #need 2.x for Python3 support python-dateutil==2.1.0 #1.1.0 is the first that can be installed on windows # Yet we decide to remove this from requirements.txt, # because ADAL does not have a direct dependency on it. #cryptography==3.2 #for testing httpretty==0.8.14 pylint==1.5.4 azure-activedirectory-library-for-python-1.2.7/sample/000077500000000000000000000000001403263465100230775ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/sample/certificate_credentials_sample.py000066400000000000000000000044671403263465100316640ustar00rootroot00000000000000import json import logging import os import sys import adal def turn_on_logging(): logging.basicConfig(level=logging.DEBUG) #or, #handler = logging.StreamHandler() #adal.set_logging_options({ # 'level': 'DEBUG', # 'handler': handler #}) #handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT)) def get_private_key(filename): with open(filename, 'r') as pem_file: private_pem = pem_file.read() return private_pem # # 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. # # The information inside such file can be obtained via app registration. # See https://github.com/AzureAD/azure-activedirectory-library-for-python/wiki/Register-your-application-with-Azure-Active-Directory # # { # "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 os.environ.get('ADAL_SAMPLE_PARAMETERS_FILE')) sample_parameters = {} if parameters_file: with open(parameters_file, 'r') as f: parameters = f.read() sample_parameters = json.loads(parameters) else: raise ValueError('Please provide parameter file with account information.') authority_url = (sample_parameters['authorityHostUrl'] + '/' + sample_parameters['tenant']) GRAPH_RESOURCE = '00000002-0000-0000-c000-000000000000' RESOURCE = sample_parameters.get('resource', GRAPH_RESOURCE) #uncomment for verbose logging turn_on_logging() ### Main logic begins context = adal.AuthenticationContext(authority_url) key = get_private_key(sample_parameters['privateKeyFile']) token = context.acquire_token_with_client_certificate( RESOURCE, sample_parameters['clientId'], key, sample_parameters['thumbprint']) ### Main logic ends print('Here is the token:') print(json.dumps(token, indent=2)) azure-activedirectory-library-for-python-1.2.7/sample/client_credentials_sample.py000066400000000000000000000037151403263465100306530ustar00rootroot00000000000000import json import logging import os import sys import adal def turn_on_logging(): logging.basicConfig(level=logging.DEBUG) #or, #handler = logging.StreamHandler() #adal.set_logging_options({ # 'level': 'DEBUG', # '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.py parameters.json', or # specifying in an environment variable of ADAL_SAMPLE_PARAMETERS_FILE. # # The information inside such file can be obtained via app registration. # See https://github.com/AzureAD/azure-activedirectory-library-for-python/wiki/Register-your-application-with-Azure-Active-Directory # # { # "resource": "YOUR_RESOURCE", # "tenant" : "YOUR_SUB_DOMAIN.onmicrosoft.com", # "authorityHostUrl" : "https://login.microsoftonline.com", # "clientId" : "YOUR_CLIENTID", # "clientSecret" : "YOUR_CLIENTSECRET" # } parameters_file = (sys.argv[1] if len(sys.argv) == 2 else os.environ.get('ADAL_SAMPLE_PARAMETERS_FILE')) if parameters_file: with open(parameters_file, 'r') as f: parameters = f.read() sample_parameters = json.loads(parameters) else: raise ValueError('Please provide parameter file with account information.') authority_url = (sample_parameters['authorityHostUrl'] + '/' + sample_parameters['tenant']) GRAPH_RESOURCE = '00000002-0000-0000-c000-000000000000' RESOURCE = sample_parameters.get('resource', GRAPH_RESOURCE) #uncomment for verbose log #turn_on_logging() ### Main logic begins context = adal.AuthenticationContext( authority_url, validate_authority=sample_parameters['tenant'] != 'adfs', ) token = context.acquire_token_with_client_credentials( RESOURCE, sample_parameters['clientId'], sample_parameters['clientSecret']) ### Main logic ends print('Here is the token:') print(json.dumps(token, indent=2)) azure-activedirectory-library-for-python-1.2.7/sample/device_code_sample.py000066400000000000000000000051271403263465100272500ustar00rootroot00000000000000import json import logging import os import sys import adal def turn_on_logging(): logging.basicConfig(level=logging.DEBUG) #or, #handler = logging.StreamHandler() #adal.set_logging_options({ # 'level': 'DEBUG', # '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.py parameters.json', or # specifying in an environment variable of ADAL_SAMPLE_PARAMETERS_FILE. # # The information inside such file can be obtained via app registration. # See https://github.com/AzureAD/azure-activedirectory-library-for-python/wiki/Register-your-application-with-Azure-Active-Directory # # { # "resource": "your_resource", # "tenant" : "rrandallaad1.onmicrosoft.com", # "authorityHostUrl" : "https://login.microsoftonline.com", # "clientId" : "624ac9bd-4c1c-4687-aec8-b56a8991cfb3", # "anothertenant" : "bar.onmicrosoft.com" # } parameters_file = (sys.argv[1] if len(sys.argv) == 2 else os.environ.get('ADAL_SAMPLE_PARAMETERS_FILE')) if parameters_file: with open(parameters_file, 'r') as f: parameters = f.read() sample_parameters = json.loads(parameters) else: raise ValueError('Please provide parameter file with account information.') authority_host_url = sample_parameters['authorityHostUrl'] authority_url = authority_host_url + '/' + sample_parameters['tenant'] clientid = sample_parameters['clientId'] GRAPH_RESOURCE = '00000002-0000-0000-c000-000000000000' RESOURCE = sample_parameters.get('resource', GRAPH_RESOURCE) #uncomment for verbose logging #turn_on_logging() ### Main logic begins context = adal.AuthenticationContext(authority_url) 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)) #try cross tenant token refreshing 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 existing_cache = context.cache context = adal.AuthenticationContext(authority_url, cache=existing_cache) token = context.acquire_token(RESOURCE, token['userId'], clientid) print('Here is the token from "{}":'.format(authority_url)) print(json.dumps(token, indent=2)) azure-activedirectory-library-for-python-1.2.7/sample/refresh_token_sample.py000066400000000000000000000047101403263465100276520ustar00rootroot00000000000000import json import logging import os import sys import adal def turn_on_logging(): logging.basicConfig(level=logging.DEBUG) #or, #handler = logging.StreamHandler() #adal.set_logging_options({ # 'level': 'DEBUG', # '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.py parameters.json', or # specifying in an environment variable of ADAL_SAMPLE_PARAMETERS_FILE. # # The information inside such file can be obtained via app registration. # See https://github.com/AzureAD/azure-activedirectory-library-for-python/wiki/Register-your-application-with-Azure-Active-Directory # # { # "resource": "your_resource", # "tenant" : "rrandallaad1.onmicrosoft.com", # "authorityHostUrl" : "https://login.microsoftonline.com", # "clientId" : "624ac9bd-4c1c-4687-aec8-b56a8991cfb3", # "username" : "user1", # "password" : "verySecurePassword" # } parameters_file = (sys.argv[1] if len(sys.argv) == 2 else os.environ.get('ADAL_SAMPLE_PARAMETERS_FILE')) if parameters_file: with open(parameters_file, 'r') as f: parameters = f.read() sample_parameters = json.loads(parameters) else: raise ValueError('Please provide parameter file with account information.') authority_url = (sample_parameters['authorityHostUrl'] + '/' + sample_parameters['tenant']) GRAPH_RESOURCE = '00000002-0000-0000-c000-000000000000' RESOURCE = sample_parameters.get('resource', GRAPH_RESOURCE) #uncomment for verbose log #turn_on_logging() ### Main logic begins context = adal.AuthenticationContext( authority_url, validate_authority=sample_parameters['tenant'] != 'adfs', ) token = context.acquire_token_with_username_password( RESOURCE, sample_parameters['username'], sample_parameters['password'], sample_parameters['clientId']) print('Here is the token') print(json.dumps(token, indent=2)) refresh_token = token['refreshToken'] token = context.acquire_token_with_refresh_token( refresh_token, sample_parameters['clientId'], 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)) azure-activedirectory-library-for-python-1.2.7/sample/website_sample.py000066400000000000000000000124451403263465100264620ustar00rootroot00000000000000try: from http import server as httpserver from http import cookies as Cookie except ImportError: import SimpleHTTPServer as httpserver import Cookie as Cookie try: import socketserver except ImportError: import SocketServer as socketserver try: from urllib.parse import urlparse, parse_qs except ImportError: from urlparse import urlparse, parse_qs import json import os import random import string import sys from adal import AuthenticationContext # 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. # # The information inside such file can be obtained via app registration. # See https://github.com/AzureAD/azure-activedirectory-library-for-python/wiki/Register-your-application-with-Azure-Active-Directory # # { # "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 os.environ.get('ADAL_SAMPLE_PARAMETERS_FILE')) if parameters_file: with open(parameters_file, 'r') as f: parameters = f.read() sample_parameters = json.loads(parameters) else: raise ValueError('Please provide parameter file with account information.') PORT = 8088 TEMPLATE_AUTHZ_URL = ('https://login.microsoftonline.com/{}/oauth2/authorize?'+ 'response_type=code&client_id={}&redirect_uri={}&'+ 'state={}&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'] + '/' + sample_parameters['tenant']) class OAuth2RequestHandler(httpserver.SimpleHTTPRequestHandler): def do_GET(self): if self.path == '/': self.send_response(307) login_url = 'http://localhost:{}/login'.format(PORT) self.send_header('Location', login_url) self.end_headers() elif self.path == '/login': 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, RESOURCE) self.send_response(307) self.send_header('Set-Cookie', cookie.output(header='')) self.send_header('Location', authorization_url) self.end_headers() elif self.path.startswith('/getAToken'): is_ok = True try: 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) 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:' + 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] state = parse_qs(parsed.query)['state'][0] 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) return auth_context.acquire_token_with_authorization_code( code, REDIRECT_URI, 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) self.send_header('Content-type', 'text/html') self.end_headers() if is_ok: #todo, pretty format token response in json message_template = ('Succeeded' '

{}

') else: message_template = ('Failed' '

{}

') output = message_template.format(message) self.wfile.write(output.encode()) httpd = socketserver.TCPServer(('', PORT), OAuth2RequestHandler) print('serving at port', PORT) httpd.serve_forever() azure-activedirectory-library-for-python-1.2.7/setup.cfg000066400000000000000000000001671403263465100234430ustar00rootroot00000000000000[bdist_wheel] universal=1 [metadata] long_description = file: README.md long_description_content_type = text/markdown azure-activedirectory-library-for-python-1.2.7/setup.py000066400000000000000000000062241403263465100233340ustar00rootroot00000000000000#!/usr/bin/env python #------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ from setuptools import setup import re, io # setup.py shall not import adal __version__ = re.search( r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', # It excludes inline comment too io.open('adal/__init__.py', encoding='utf_8_sig').read() ).group(1) # To build: # python setup.py sdist # python setup.py bdist_wheel # # To install: # python setup.py install # # To register (only needed once): # python setup.py register # # To upload: # python setup.py sdist upload # python setup.py bdist_wheel upload setup( name='adal', version=__version__, description=('Note: This library is already replaced by MSAL Python, ' + 'available here: https://pypi.org/project/msal/ .' + 'ADAL Python remains available here as a legacy. ' + 'The ADAL for Python library makes it easy for python ' + 'application to authenticate to Azure Active Directory ' + '(AAD) in order to access AAD protected web resources.'), license='MIT', author='Microsoft Corporation', author_email='nugetaad@microsoft.com', url='https://github.com/AzureAD/azure-activedirectory-library-for-python', classifiers=[ 'Development Status :: 6 - Mature', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'License :: OSI Approved :: MIT License', ], packages=['adal'], install_requires=[ 'PyJWT>=1.0.0,<3', 'requests>=2.0.0,<3', 'python-dateutil>=2.1.0,<3', 'cryptography>=1.1.0' ] ) azure-activedirectory-library-for-python-1.2.7/tests/000077500000000000000000000000001403263465100227605ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/tests/__init__.py000066400000000000000000000047221403263465100250760ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 sys import os from adal import authentication_parameters if sys.version_info[:2] < (2, 7, ): try: from unittest2 import TestLoader, TextTestRunner except ImportError: raise ImportError("The ADAL test suite requires the unittest2 " "package to run on Python 2.6 and below.\n" "Please install this package to continue.") else: from unittest import TestLoader, TextTestRunner if sys.version_info[:2] >= (3, 3, ): from unittest import mock else: try: import mock except ImportError: raise ImportError("The ADAL test suite requires the mock " "package to run on Python 3.2 and below.\n" "Please install this package to continue.") if __name__ == '__main__': runner = TextTestRunner(verbosity=2) test_dir = os.path.dirname(__file__) top_dir = os.path.dirname(os.path.dirname(test_dir)) test_loader = TestLoader() suite = test_loader.discover(test_dir, pattern="test_*.py", top_level_dir=top_dir) runner.run(suite) azure-activedirectory-library-for-python-1.2.7/tests/config_sample.py000066400000000000000000000075461403263465100261540ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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. # #------------------------------------------------------------------------------ ''' This is a sample config file. Make a copy of it as config.py. Then follow the provided instructions to fill in your values. ''' ACQUIRE_TOKEN_WITH_USERNAME_PASSWORD = { # Getting token with username and passwords is the simple method. You need to create an Azure # Active Directory and a user. Once you have done this, you can put the tenant name, username # and password combination here. # # Note: You need to attempt to login to the user at least once to create a non-temp password. # To do this, go to http://manage.azure.com, sign in, create a new password, and use # the password created here. "username" : "USERNAME@XXXXXXXX.onmicrosoft.com", "password" : "None", "tenant" : "XXXXXXXX.onmicrosoft.com", "authorityHostUrl" : "https://login.microsoftonline.com", } ACQUIRE_TOKEN_WITH_CLIENT_CREDENTIALS = { # To use client credentials (Secret Key) you need to: # Create an Azure Active Directory (AD) instance on your Azure account # in this AD instance, create an application. I call mine PythonSDK http://PythonSDK # Go to the configure tab and you can find all of the following information: # Click on 'View Endpoints' and Copy the 'Federation Metadata Document' entry. # The root + GUID URL is our authority. "authority" : "https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL", # Exit out of App Endpoints. The client Id is on the configure page. "client_id" : "ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL", # In the keys section of the Azure AD App Configure page, create a key (1 or 2 years is fine) "secret" : "a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a=", # NOTE: If this is to be used with ARM (the case of the Azure SDK) you will need to grant # permissions to that service. At this time Azure does not have this in the portal for # Azure Resource Management. # Here is an example using POSH (Powershell) Tools for Azure to grant those rights. # Switch-AzureMode -Name AzureResourceManager # Add-AzureAccount # This will pop up a login dialog # # Look at the subscriptions returned and put one on the line below # Select-AzureSubscription -SubscriptionId ABCDEFGH-1234-1234-1234-ABCDEFGH # New-AzureRoleAssignment -ServicePrincipalName http://PythonSDK -RoleDefinitionName Contributor } # TODO: ADD DICTIONARIES FOR THE OTHER TESTS # ACQUIRE_TOKEN_WITH_AUTHORIZATION_CODE # ACQUIRE_TOKEN_WITH_REFRESH_TOKEN # ACQUIRE_TOKEN_WITH_CLIENT_CERTIFICATE azure-activedirectory-library-for-python-1.2.7/tests/mex/000077500000000000000000000000001403263465100235515ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/tests/mex/address.insecure.xml000066400000000000000000001471201403263465100275410ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://arvmserver2012.archan.us/adfs/services/trust/2005/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatetransport https://arvmserver2012.archan.us/adfs/services/trust/2005/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/13/certificatemixed http://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 http://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us azure-activedirectory-library-for-python-1.2.7/tests/mex/archan.us.mex.xml000066400000000000000000001457541403263465100267650ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://arvmserver2012.archan.us/adfs/services/trust/2005/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatetransport https://arvmserver2012.archan.us/adfs/services/trust/2005/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/13/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us azure-activedirectory-library-for-python-1.2.7/tests/mex/arupela.mex.xml000066400000000000000000001404651403263465100265260ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://fs.arupela.com/adfs/services/trust/2005/windowstransport host/fs.arupela.com https://fs.arupela.com/adfs/services/trust/2005/certificatemixed https://fs.arupela.com:49443/adfs/services/trust/2005/certificatetransport https://fs.arupela.com/adfs/services/trust/2005/usernamemixed https://fs.arupela.com/adfs/services/trust/2005/kerberosmixed https://fs.arupela.com/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://fs.arupela.com/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://fs.arupela.com/adfs/services/trust/13/kerberosmixed https://fs.arupela.com/adfs/services/trust/13/certificatemixed https://fs.arupela.com/adfs/services/trust/13/usernamemixed https://fs.arupela.com/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://fs.arupela.com/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 azure-activedirectory-library-for-python-1.2.7/tests/mex/common.mex.xml000066400000000000000000001461641403263465100263670ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://adfs.federatedtenant.com/adfs/services/trust/2005/windowstransport iamfed@federatedtenant.com https://adfs.federatedtenant.com/adfs/services/trust/2005/certificatemixed https://adfs.federatedtenant.com/adfs/services/trust/2005/usernamemixed https://adfs.federatedtenant.com/adfs/services/trust/2005/kerberosmixed https://adfs.federatedtenant.com/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://adfs.federatedtenant.com/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://adfs.federatedtenant.com/adfs/services/trust/13/kerberosmixed https://adfs.federatedtenant.com/adfs/services/trust/13/certificatemixed https://adfs.federatedtenant.com/adfs/services/trust/13/usernamemixed https://adfs.federatedtenant.com/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://adfs.federatedtenant.com/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 https://adfs.federatedtenant.com/adfs/services/trust/13/windowstransport iamfed@federatedtenant.com azure-activedirectory-library-for-python-1.2.7/tests/mex/microsoft.mex.xml000066400000000000000000001461141403263465100270770ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://corp.sts.microsoft.com/adfs/services/trust/2005/windowstransport iamfed@redmond.corp.microsoft.com https://corp.sts.microsoft.com/adfs/services/trust/2005/certificatemixed https://corp.sts.microsoft.com/adfs/services/trust/2005/usernamemixed https://corp.sts.microsoft.com/adfs/services/trust/2005/kerberosmixed https://corp.sts.microsoft.com/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://corp.sts.microsoft.com/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://corp.sts.microsoft.com/adfs/services/trust/13/kerberosmixed https://corp.sts.microsoft.com/adfs/services/trust/13/certificatemixed https://corp.sts.microsoft.com/adfs/services/trust/13/usernamemixed https://corp.sts.microsoft.com/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://corp.sts.microsoft.com/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 https://corp.sts.microsoft.com/adfs/services/trust/13/windowstransport iamfed@redmond.corp.microsoft.com azure-activedirectory-library-for-python-1.2.7/tests/mex/noaddress.xml000066400000000000000000001465531403263465100262730ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://arvmserver2012.archan.us/adfs/services/trust/2005/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatetransport https://arvmserver2012.archan.us/adfs/services/trust/2005/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/13/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us azure-activedirectory-library-for-python-1.2.7/tests/mex/nobinding.port.xml000066400000000000000000001470551403263465100272410ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://arvmserver2012.archan.us/adfs/services/trust/2005/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatetransport https://arvmserver2012.archan.us/adfs/services/trust/2005/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/13/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us azure-activedirectory-library-for-python-1.2.7/tests/mex/noname.binding.xml000066400000000000000000001471141403263465100271710ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://arvmserver2012.archan.us/adfs/services/trust/2005/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatetransport https://arvmserver2012.archan.us/adfs/services/trust/2005/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/13/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us azure-activedirectory-library-for-python-1.2.7/tests/mex/nosoapaction.xml000066400000000000000000001455631403263465100270060ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://arvmserver2012.archan.us/adfs/services/trust/2005/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatetransport https://arvmserver2012.archan.us/adfs/services/trust/2005/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/13/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us azure-activedirectory-library-for-python-1.2.7/tests/mex/nosoaptransport.xml000066400000000000000000001456731403263465100275670ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://arvmserver2012.archan.us/adfs/services/trust/2005/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatetransport https://arvmserver2012.archan.us/adfs/services/trust/2005/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/13/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us azure-activedirectory-library-for-python-1.2.7/tests/mex/nouri.ref.xml000066400000000000000000001457001403263465100262110ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://arvmserver2012.archan.us/adfs/services/trust/2005/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatetransport https://arvmserver2012.archan.us/adfs/services/trust/2005/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/13/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us azure-activedirectory-library-for-python-1.2.7/tests/mex/syntax.notrelated.mex.xml000066400000000000000000001457521403263465100305670ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://arvmserver2012.archan.us/adfs/services/trust/2005/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatetransport https://arvmserver2012.archan.us/adfs/services/trust/2005/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/13/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransport host/ARVMServer2012.archan.us azure-activedirectory-library-for-python-1.2.7/tests/mex/syntax.related.mex.xml000066400000000000000000001457531403263465100300470ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://arvmserver2012.archan.us/adfs/services/trust/2005/windowstransport host/ARVMServer2012.archan.us https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/certificatetransport https://arvmserver2012.archan.us/adfs/services/trust/2005/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/2005/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/kerberosmixed https://arvmserver2012.archan.us/adfs/services/trust/13/certificatemixed https://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 https://arvmserver2012.archan.us/adfs/services/trust/13/windowstransportdwsa10:Address> host/ARVMServer2012.archan.us azure-activedirectory-library-for-python-1.2.7/tests/mex/usystech.mex.xml000066400000000000000000001173421403263465100267420ustar00rootroot00000000000000 http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2000/09/xmldsig#rsa-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey 256 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p http://www.w3.org/2001/04/xmlenc#aes256-cbc http://www.w3.org/2000/09/xmldsig#hmac-sha1 http://www.w3.org/2001/10/xml-exc-c14n# http://www.w3.org/2001/04/xmlenc#aes256-cbc https://sts.usystech.net/adfs/services/trust/2005/windowstransport host/sts.usystech.net https://sts.usystech.net/adfs/services/trust/2005/certificatemixed https://sts.usystech.net:49443/adfs/services/trust/2005/certificatetransport http://sts.usystech.net/adfs/services/trust/2005/username MIIFDTCCA/WgAwIBAgIQB64+CRmpRmmV6WZdZEpD+jANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5EaWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTUwNjIzMDAwMDAwWhcNMTYwNzExMTIwMDAwWjBbMQswCQYDVQQGEwJVUzENMAsGA1UECBMEVXRhaDEOMAwGA1UEBxMFU2FuZHkxFDASBgNVBAoTC0ZyZWQgRHVhcnRlMRcwFQYDVQQDDA4qLnVzeXN0ZWNoLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMJkd0UKklEa0oRZElGJw9ydFKZtpCOy6KMfQOKBk20a1wFBwWgSEza+sL4ykLeZTo+J+7QuNXs/JaT3eXcJGRGLKSXJSQ2K2CQkNgb7VzOjlRn5rJrgG0wKuEAzA376odoxIUppUdgGNYcBVd2Lfkct63Pff55Qi+K1PDqrOwtJ/YuIfRlZNktgO6NaTsGBQQLmzh2zXZyGMJ5iObwv9F5/RhtlZKAu37B6DPzJv8jfcjOOFWa4wyxBlRV1GacypPCXGUI81cfTb1eRA97Cwx2EEM1QcP2OQXHjJ3DIlgHzL+ZY6KFoU3rkzA5/R1q/s14FGSTepwuDi6IkeuPm2ZcCAwEAAaOCAdkwggHVMB8GA1UdIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT/B07Ibzv4xAj47dCQyqSGKI3coTAnBgNVHREEIDAegg4qLnVzeXN0ZWNoLm5ldIIMdXN5c3RlY2gubmV0MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYDVR0fBGQwYjAvoC2gK4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nNC5jcmwwL6AtoCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNoYTItZzQuY3JsMEIGA1UdIAQ7MDkwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAS+pFrDx+MWkLSv7ZymBm/i3BtO9ko+bop8d6I8jzEYGd4p4pwovARw0olGjubdbtuVx7HV1HzKyBVA4U+Pz1FgMAD43mcysg02FdZyeaIOCyqPN4m3Cnk1ml5n10My6en9pM5DUwnF4/R9AFJ2uvaGlurBRKGGbd1skH1bdl7QF5UTtyP+c+mw6bznONMo3G2O0NJy3ZVJf4vHbFjSU/5JHvZH8jXAaoYPxddQ88T3zF6zDXC75OqZtB1TrF1XIqaq2kPdy7r48N5X/fCXkHwnAx1+wQhSwbbt0yfhYWWkbaS8A503VrNAtKLch2aQYVmzg2guh4fq9RPfgBTbD7FA== https://sts.usystech.net/adfs/services/trust/2005/usernamemixed https://sts.usystech.net/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256 https://sts.usystech.net/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256 https://sts.usystech.net/adfs/services/trust/13/certificatemixed https://sts.usystech.net/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 https://sts.usystech.net/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 azure-activedirectory-library-for-python-1.2.7/tests/test_api_version.py000066400000000000000000000065231403263465100267150ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 warnings try: import unittest2 as unittest except ImportError: import unittest try: from unittest import mock except ImportError: import mock import adal class TestAuthenticationContextApiVersionBehavior(unittest.TestCase): def test_api_version_default_value(self): with warnings.catch_warnings(record=True) as caught_warnings: warnings.simplefilter("always") context = adal.AuthenticationContext( "https://login.microsoftonline.com/tenant") self.assertEqual(context._call_context['api_version'], None) self.assertEqual(len(caught_warnings), 0) if len(caught_warnings) == 1: # It should be len(caught_warnings)==1, but somehow it works on # all my local test environment but not on Travis-CI. # So we relax this check, for now. self.assertIn("deprecated", str(caught_warnings[0].message)) def test_explicitly_turn_off_api_version(self): with warnings.catch_warnings(record=True) as caught_warnings: warnings.simplefilter("always") context = adal.AuthenticationContext( "https://login.microsoftonline.com/tenant", api_version=None) self.assertEqual(context._call_context['api_version'], None) self.assertEqual(len(caught_warnings), 0) class TestOAuth2ClientApiVersionBehavior(unittest.TestCase): authority = mock.Mock(token_endpoint="https://example.com/token") def test_api_version_is_set(self): client = adal.oauth2_client.OAuth2Client( {"api_version": "1.0", "log_context": mock.Mock()}, self.authority) self.assertIn('api-version=1.0', client._create_token_url().geturl()) def test_api_version_is_not_set(self): client = adal.oauth2_client.OAuth2Client( {"api_version": None, "log_context": mock.Mock()}, self.authority) self.assertNotIn('api-version=1.0', client._create_token_url().geturl()) if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_authentication_parameters.py000066400000000000000000000311131403263465100316320ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 sys import requests import httpretty try: import unittest2 as unittest except ImportError: import unittest try: from unittest import mock except ImportError: import mock try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse import adal from tests import util from tests.util import parameters as cp class TestAuthenticationParameters(unittest.TestCase): def setUp(self): testHost = 'https://this.is.my.domain.com' testPath = '/path/to/resource' testQuery = 'a=query&string=really' self.testUrl = testHost + testPath + '?' + testQuery return super(TestAuthenticationParameters, self).setUp() def run_data(self, test_data, test_func): for index, test_case in enumerate(test_data): parameters = None error = None test_input = test_case[0] test_params = test_case[1] try: parameters = test_func(test_input) except Exception as exp: error = exp prefix = "Test case: {0} - ".format(index) if test_params: self.assertIsNone(error, "{0}Parse failed but should have succeeded. {1}".format(prefix, error)) self.assertEqual(parameters.authorization_uri, test_params.get('authorizationUri'), "{0}Parsed authorizationUri did not match expected value.: {1}".format(prefix, parameters.authorization_uri)) self.assertEqual(parameters.resource, test_params.get('resource'), "{0}Parsed resource did not match expected value.: {1}".format(prefix, parameters.resource)) else: self.assertIsNotNone(error, "{0}Parse succeeded but should have failed.".format(prefix)) def test_create_from_header(self): test_data = [ [ 'Bearer authorization_uri="foobar,lkfj,;l,", fruitcake="f",resource="clark, &^()- q32,shark" , f="foo"', { 'authorizationUri' : 'foobar,lkfj,;l,', 'resource' : 'clark, &^()- q32,shark', } ], [ 'Bearer resource="clark, &^()- q32,shark", authorization_uri="foobar,lkfj,;l,"', { 'authorizationUri' : 'foobar,lkfj,;l,', 'resource' : 'clark, &^()- q32,shark', } ], [ 'Bearer authorization_uri="' + cp['authorityTenant'] + '", resource="' + cp['resource'] + '"', { 'authorizationUri' : cp['authorityTenant'], 'resource' : cp['resource'], } ], [ 'Bearer authorization_uri="' + cp['authorizeUrl'] + '", resource="' + cp['resource'] + '"', { 'authorizationUri' : cp['authorizeUrl'], 'resource' : cp['resource'], } ], # Add second = sign on first pair. [ 'Bearer authorization_uri=="foobar,lkfj,;l,", resource="clark, &^()- q32,shark",fruitcake="f" , f="foo"', None ], # Add second = sign on second pair. [ 'Bearer authorization_uri="foobar,lkfj,;l,", resource=="clark, &^()- q32,shark",fruitcake="f" , f="foo"', None ], # Add second quote on first pair. [ 'Bearer authorization_uri=""foobar,lkfj,;l,", resource="clark, &^()- q32,shark",fruitcake="f" , f="foo"', None ], # Add second quote on second pair. [ 'Bearer authorization_uri=foobar,lkfj,;l,", resource="clark, &^()- q32,shark"",fruitcake="f" , f="foo"', None ], # Add trailing quote. [ 'Bearer authorization_uri=foobar,lkfj,;l,", resource="clark, &^()- q32,shark",fruitcake="f" , f="foo""', None ], # Add trailing comma at end of string. [ 'Bearer authorization_uri=foobar,lkfj,;l,", resource="clark, &^()- q32,shark",fruitcake="f" , f="foo",', None ], # Add second comma between 2 and 3 pairs. [ 'Bearer authorization_uri=foobar,lkfj,;l,", resource="clark, &^()- q32,shark",fruitcake="f" ,, f="foo"', None ], # Add second comma between 1 and 2 pairs. [ 'Bearer authorization_uri=foobar,lkfj,;l,", , resource="clark, &^()- q32,shark",fruitcake="f" , f="foo"', None ], # Add random letter between Bearer and first pair. [ 'Bearer f authorization_uri=foobar,lkfj,;l,", resource="clark, &^()- q32,shark",fruitcake="f" , f="foo"', None ], # Add random letter between 2 and 3 pair. [ 'Bearer authorization_uri=foobar,lkfj,;l,", a resource="clark, &^()- q32,shark",fruitcake="f" , f="foo"', None ], # Add random letter between 3 and 2 pair. [ 'Bearer authorization_uri=foobar,lkfj,;l,", resource="clark, &^()- q32,shark",fruitcake="f" a, f="foo"', None ], # Mispell Bearer [ 'Berer authorization_uri=foobar,lkfj,;l,", resource="clark, &^()- q32,shark",fruitcake="f" , f="foo"', None ], # Missing resource. [ 'Bearer authorization_uri="foobar,lkfj,;l,"', { 'authorizationUri' : 'foobar,lkfj,;l,' } ], # Missing authoritzation uri. [ 'Bearer resource="clark, &^()- q32,shark",fruitcake="f" , f="foo"', None ], # Boris's test. [ 'Bearer foo="bar" ANYTHING HERE, ANYTHING PRESENT HERE, foo1="bar1"', None ], [ 'Bearerauthorization_uri="authuri", resource="resourceHere"', None ], ] self.run_data(test_data, adal.authentication_parameters.create_authentication_parameters_from_header) def test_create_from_response(self): test_data = [ [ mock.Mock(status_code=401, headers={ 'www-authenticate' : 'Bearer authorization_uri="foobar,lkfj,;l,", fruitcake="f",resource="clark, &^()- q32,shark" , f="foo"' }), { 'authorizationUri' : 'foobar,lkfj,;l,', 'resource' : 'clark, &^()- q32,shark', } ], [ mock.Mock(status_code=200, headers={ 'www-authenticate' : 'Bearer authorization_uri="foobar,lkfj,;l,", fruitcake="f",resource="clark, &^()- q32,shark" , f="foo"' }), None ], [ mock.Mock(status_code=401), None ], [ mock.Mock(status_code=401, headers={ 'foo' : 'this is not the www-authenticate header' }), None ], [ mock.Mock(status_code=401, headers={ 'www-authenticate' : 'Berer authorization_uri=foobar,lkfj,;l,", resource="clark, &^()- q32,shark",fruitcake="f" , f="foo"' }), None ], [ mock.Mock(status_code=401, headers={ 'www-authenticate' : None }), None ], [ mock.Mock(headers={ 'www-authenticate' : None }), None ], [ None, None ] ] self.run_data(test_data, adal.authentication_parameters.create_authentication_parameters_from_response) @httpretty.activate def test_create_from_url_happy_string_url(self): httpretty.register_uri(httpretty.GET, uri=self.testUrl, body='foo', status=401, **{'www-authenticate':'Bearer authorization_uri="foobar,lkfj,;l,", fruitcake="f",resource="clark, &^()- q32,shark" , f="foo"'}) # maybe try-catch here to catch the error parameters = adal.authentication_parameters.create_authentication_parameters_from_url(self.testUrl) test_params = { 'authorizationUri' : 'foobar,lkfj,;l,', 'resource' : 'clark, &^()- q32,shark', } self.assertEqual(parameters.authorization_uri, test_params['authorizationUri'], 'Parsed authorizationUri did not match expected value.: {0}'.format(parameters.authorization_uri)) self.assertEqual(parameters.resource, test_params['resource'], 'Parsed resource did not match expected value.: {0}'.format(parameters.resource)) req = httpretty.last_request() util.match_standard_request_headers(req) @httpretty.activate def test_create_from_url_happy_path_url_object(self): httpretty.register_uri(httpretty.GET, uri=self.testUrl, body='foo', status=401, **{'www-authenticate':'Bearer authorization_uri="foobar,lkfj,;l,", fruitcake="f",resource="clark, &^()- q32,shark" , f="foo"'}) url_obj = urlparse(self.testUrl) try: parameters = adal.authentication_parameters.create_authentication_parameters_from_url(url_obj) test_params = { 'authorizationUri' : 'foobar,lkfj,;l,', 'resource' : 'clark, &^()- q32,shark', } self.assertEqual(parameters.authorization_uri, test_params['authorizationUri'], 'Parsed authorizationUri did not match expected value.: {0}'.format(parameters.authorization_uri)) self.assertEqual(parameters.resource, test_params['resource'], 'Parsed resource did not match expected value.: {0}'.format(parameters.resource)) except Exception as err: self.assertIsNone(err, "An error was raised during function {0}".format(err)) req = httpretty.last_request() util.match_standard_request_headers(req) def test_create_from_url_bad_object(self): try: parameters = adal.authentication_parameters.create_authentication_parameters_from_url({}) except Exception as exp: self.assertIsNotNone(exp, "Did not receive expected error.") pass def test_create_from_url_not_passed(self): try: parameters = adal.authentication_parameters.create_authentication_parameters_from_url(None) except Exception as exp: self.assertIsNotNone(exp, "Did not receive expected error.") pass @httpretty.activate def test_create_from_url_no_header(self): httpretty.register_uri(httpretty.GET, uri=self.testUrl, body='foo', status=401) receivedException = False try: adal.authentication_parameters.create_authentication_parameters_from_url(self.testUrl) except Exception as err: receivedException = True self.assertTrue(str(err).find('header') >= 0, 'Error did not include message about missing header') pass finally: self.assertTrue(receivedException) req = httpretty.last_request() util.match_standard_request_headers(req) def test_create_from_url_network_error(self): try: adal.authentication_parameters.create_authentication_parameters_from_url('https://0.0.0.0/foobar') except Exception as err: self.assertIsNotNone(err, "Did not receive expected error.") if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_authority.py000066400000000000000000000225611403263465100264270ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 sys import requests import httpretty import six try: import unittest2 as unittest except ImportError: import unittest try: from unittest import mock except ImportError: import mock import adal from adal.authority import Authority from adal import log from adal.authentication_context import AuthenticationContext from tests import util from tests.util import parameters as cp try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse class TestAuthority(unittest.TestCase): # use this as authority to force dynamic as opposed to static instance # discovery. nonHardCodedAuthority = 'https://login.doesntexist.com/' + cp['tenant'] nonHardCodedAuthorizeEndpoint = nonHardCodedAuthority + '/oauth2/authorize' dstsTestEndpoint = 'https://test-dsts.dsts.core.azure-test.net/dstsv2/common' def setUp(self): util.reset_logging() util.clear_static_cache() return super(TestAuthority, self).setUp() def tearDown(self): util.reset_logging() util.clear_static_cache() return super(TestAuthority, self).tearDown() def setupExpectedInstanceDiscoveryRequestRetries(self, requestParametersList, authority): pass @httpretty.activate def test_success_dynamic_instance_discovery(self): instanceDiscoveryRequest = util.setup_expected_instance_discovery_request( 200, cp['authorityHosts']['global'], {'tenant_discovery_endpoint' : 'http://foobar'}, self.nonHardCodedAuthorizeEndpoint ) responseOptions = { 'authority' : self.nonHardCodedAuthority } response = util.create_response(responseOptions) wireResponse = response['wireResponse'] util.setup_expected_client_cred_token_request_response(200, wireResponse, self.nonHardCodedAuthority) context = adal.AuthenticationContext(self.nonHardCodedAuthority) token_response = context.acquire_token_with_client_credentials( response['resource'], cp['clientId'], cp['clientSecret']) self.assertTrue( util.is_match_token_response(response['cachedResponse'], token_response), 'The response does not match what was expected.: ' + str(token_response) ) def performStaticInstanceDiscovery(self, authorityHost): hardCodedAuthority = 'https://' + authorityHost + '/' + cp['tenant'] responseOptions = { 'authority' : hardCodedAuthority } response = util.create_response(responseOptions) wireResponse = response['wireResponse'] tokenRequest = util.setup_expected_client_cred_token_request_response(200, wireResponse, hardCodedAuthority) context = adal.AuthenticationContext(hardCodedAuthority) token_response = context.acquire_token_with_client_credentials( response['resource'], cp['clientId'], cp['clientSecret']) self.assertTrue( util.is_match_token_response(response['cachedResponse'], token_response), 'The response does not match what was expected.: ' + str(token_response) ) @httpretty.activate def test_success_static_instance_discovery(self): self.performStaticInstanceDiscovery('login.microsoftonline.com') self.performStaticInstanceDiscovery('login.chinacloudapi.cn') self.performStaticInstanceDiscovery('login.microsoftonline.us') self.performStaticInstanceDiscovery('test-dsts.dsts.core.windows.net') self.performStaticInstanceDiscovery('test-dsts.dsts.core.chinacloudapi.cn') self.performStaticInstanceDiscovery('test-dsts.dsts.core.cloudapi.de') self.performStaticInstanceDiscovery('test-dsts.dsts.core.usgovcloudapi.net') self.performStaticInstanceDiscovery('test-dsts.dsts.core.azure-test.net') @httpretty.activate def test_http_error(self): util.setup_expected_instance_discovery_request(500, cp['authorityHosts']['global'], None, self.nonHardCodedAuthorizeEndpoint) with six.assertRaisesRegex(self, Exception, '500'): context = adal.AuthenticationContext(self.nonHardCodedAuthority) token_response = context.acquire_token_with_client_credentials( cp['resource'], cp['clientId'], cp['clientSecret']) @httpretty.activate def test_validation_error(self): returnDoc = { 'error' : 'invalid_instance', 'error_description' : 'the instance was invalid' } util.setup_expected_instance_discovery_request(400, cp['authorityHosts']['global'], returnDoc, self.nonHardCodedAuthorizeEndpoint) with six.assertRaisesRegex(self, Exception, 'instance was invalid'): context = adal.AuthenticationContext(self.nonHardCodedAuthority) token_response = context.acquire_token_with_client_credentials( cp['resource'], cp['clientId'], cp['clientSecret']) @httpretty.activate def test_validation_off(self): instanceDiscoveryRequest = util.setup_expected_instance_discovery_request( 200, cp['authorityHosts']['global'], {'tenant_discovery_endpoint' : 'http://foobar'}, self.nonHardCodedAuthorizeEndpoint ) responseOptions = { 'authority' : self.nonHardCodedAuthority} response = util.create_response(responseOptions) wireResponse = response['wireResponse'] util.setup_expected_client_cred_token_request_response(200, wireResponse, self.nonHardCodedAuthority) context = adal.AuthenticationContext(self.nonHardCodedAuthority) token_response = context.acquire_token_with_client_credentials( response['resource'], cp['clientId'], cp['clientSecret']) self.assertTrue( util.is_match_token_response(response['cachedResponse'], token_response), 'The response does not match what was expected.: ' + str(token_response) ) @httpretty.activate def test_bad_url_not_https(self): with six.assertRaisesRegex(self, ValueError, "The authority url must be an https endpoint\."): context = AuthenticationContext('http://this.is.not.https.com/mytenant.com') @httpretty.activate def test_bad_url_has_query(self): with six.assertRaisesRegex(self, ValueError, "The authority url must not have a query string\."): context = AuthenticationContext(cp['authorityTenant'] + '?this=should¬=be&here=foo') @httpretty.activate def test_url_extra_path_elements(self): with six.assertRaisesRegex(self, ValueError, "tenant"): # Some tenant specific error message context = AuthenticationContext(self.nonHardCodedAuthority + '/extra/path') @httpretty.activate def test_dsts_authority(self): try: context = AuthenticationContext(self.dstsTestEndpoint) except: self.fail("AuthenticationContext() rased an exception on dstsTestEndpoint") @httpretty.activate def test_url_extra_slashes(self): util.setup_expected_instance_discovery_request(200, cp['authorityHosts']['global'], { 'tenant_discovery_endpoint': 'http://foobar' }, self.nonHardCodedAuthorizeEndpoint) authority_url = self.nonHardCodedAuthority + '/' # This should pass for one or more than one slashes authority = Authority(authority_url, True) obj = util.create_empty_adal_object() authority.validate(obj['call_context']) req = httpretty.last_request() util.match_standard_request_headers(req) @httpretty.activate def test_url_extra_slashes_change_authority_url(self): authority_url = self.nonHardCodedAuthority + '/' # This should pass for one or more than one slashes authority = Authority(authority_url, True) self.assertTrue(authority._url.geturl(), self.nonHardCodedAuthority) if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_authorization_code.py000066400000000000000000000112721403263465100302660ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 sys import requests import httpretty try: import unittest2 as unittest except ImportError: import unittest try: from unittest import mock except ImportError: import mock import adal from adal.authentication_context import AuthenticationContext from tests import util from tests.util import parameters as cp try: from urllib.parse import urlparse, urlencode except ImportError: from urllib import urlencode from urlparse import urlparse class TestAuthorizationCode(unittest.TestCase): def setup_expected_auth_code_token_request_response(self, httpCode, returnDoc, authorityEndpoint=None): if authorityEndpoint is None: authorityEndpoint = '{}{}?slice=testslice&api-version=1.0'.format(cp['authUrl'], cp['tokenPath']) queryParameters = {} queryParameters['grant_type'] = 'authorization_code' queryParameters['code'] = self.authorization_code queryParameters['client_id'] = cp['clientId'] queryParameters['client_secret'] = cp['clientSecret'] queryParameters['resource'] = cp['resource'] queryParameters['redirect_uri'] = self.redirect_uri query = urlencode(queryParameters) def func(body): return util.filter_query_strings(query, body) import json returnDocJson = json.dumps(returnDoc) httpretty.register_uri(httpretty.POST, authorityEndpoint, returnDocJson, status = httpCode, content_type = 'text/json') def setUp(self): self.authorization_code = '1234870909' self.redirect_uri = 'app_bundle:foo.bar.baz' @httpretty.activate def test_happy_path(self): response = util.create_response() self.setup_expected_auth_code_token_request_response(200, response['wireResponse']) context = adal.AuthenticationContext(cp['authUrl']) # action token_response = context.acquire_token_with_authorization_code(self.authorization_code, self.redirect_uri, response['resource'], cp['clientId'], cp['clientSecret']) # assert # the caching layer adds a few extra fields, let us pop them out for easier verification for k in ['_clientId', '_authority', 'resource']: token_response.pop(k, None) self.assertTrue(util.is_match_token_response(response['decodedResponse'], token_response), 'The response did not match what was expected') # verify a request on the wire was made req = httpretty.last_request() util.match_standard_request_headers(req) # verify the same token entry was cached cached_items = list(context.cache.read_items()) self.assertTrue(len(cached_items) == 1) _, cached_entry = cached_items[0] self.assertEqual(cached_entry, token_response) def test_failed_http_request(self): with self.assertRaises(Exception): adal._acquire_token_with_authorization_code( 'https://0.1.1.1:12/my.tenant.com', cp['clientId'], cp['clientSecret'], self.authorization_code, self.redirect_uri, response['resource']) def test_bad_argument(self): with self.assertRaises(Exception): adal._acquire_token_with_authorization_code( 'https://0.1.1.1:12/my.tenant.com', cp['clientId'], cp['clientSecret'], self.authorization_code, self.redirect_uri, 'BogusResource') if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_cache_driver.py000066400000000000000000000047771403263465100270260ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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) azure-activedirectory-library-for-python-1.2.7/tests/test_client_credentials.py000066400000000000000000000167271403263465100302410ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 import json import httpretty import six import adal from adal.self_signed_jwt import SelfSignedJwt from adal.authentication_context import AuthenticationContext from tests import util from tests.util import parameters as cp class TestClientCredentials(unittest.TestCase): def setUp(self): util.reset_logging() util.clear_static_cache() def tearDown(self): util.reset_logging() util.clear_static_cache() @httpretty.activate def test_happy_path(self): response_options = { 'noRefresh' : True, 'tokenEndpoint': True } response = util.create_response(response_options) token_request = util.setup_expected_client_cred_token_request_response(200, response['wireResponse']) context = adal.AuthenticationContext(cp['authUrl']) token_response = context.acquire_token_with_client_credentials( response['resource'], cp['clientId'], cp['clientSecret']) self.assertTrue( util.is_match_token_response(response['cachedResponse'], token_response), 'The response does not match what was expected.: ' + str(token_response) ) @httpretty.activate def test_http_error(self): tokenRequest = util.setup_expected_client_cred_token_request_response(403) with six.assertRaisesRegex(self, Exception, '403'): context = adal.AuthenticationContext(cp['authUrl']) token_response = context.acquire_token_with_client_credentials( cp['resource'], cp['clientId'], cp['clientSecret']) @httpretty.activate def test_oauth_error(self): errorResponse = { 'error' : 'invalid_client', 'error_description' : 'This is a test error description', 'error_uri' : 'http://errordescription.com/invalid_client.html' } tokenRequest = util.setup_expected_client_cred_token_request_response(400, errorResponse) with six.assertRaisesRegex(self, Exception, 'Get Token request returned http error: 400 and server response:'): context = adal.AuthenticationContext(cp['authUrl']) token_response = context.acquire_token_with_client_credentials( cp['resource'], cp['clientId'], cp['clientSecret']) @httpretty.activate def test_error_with_junk_return(self): junkResponse = 'This is not properly formated return value.' tokenRequest = util.setup_expected_client_cred_token_request_response(400, junkResponse) with self.assertRaises(Exception): context = adal.AuthenticationContext(cp['authUrl']) token_response = context.acquire_token_with_client_credentials( cp['resource'], cp['clientId'], cp['clientSecret']) @httpretty.activate def test_success_with_junk_return(self): junkResponse = 'This is not properly formated return value.' tokenRequest = util.setup_expected_client_cred_token_request_response(200, junkResponse) with self.assertRaises(Exception): context = adal.AuthenticationContext(cp['authUrl']) token_response = context.acquire_token_with_client_credentials( cp['resource'], cp['clientId'], cp['clientSecret']) def test_no_cached_token_found_error(self): context = AuthenticationContext(cp['authUrl']) try: context.acquire_token(cp['resource'], 'unknownUser', cp['clientId']) except Exception as err: self.assertTrue(err, 'Expected an error and non was recieved.') self.assertIn('not found', err.args[0], 'Returned error did not contain expected message: ' + err.args[0]) def update_self_signed_jwt_stubs(): ''' function updateSelfSignedJwtStubs() { savedProto = {} savedProto._getDateNow = SelfSignedJwt._getDateNow savedProto._getNewJwtId = SelfSignedJwt._getNewJwtId SelfSignedJwt.prototype._getDateNow = function() { return cp['nowDate'] } SelfSignedJwt.prototype._getNewJwtId = function() { return cp['jwtId'] } return savedProto } ''' raise NotImplementedError() def reset_self_signed_jwt_stubs(safe_proto): ''' function resetSelfSignedJwtStubs(saveProto) { _.extend(SelfSignedJwt, saveProto) } ''' raise NotImplementedError() @unittest.skip('https://github.com/AzureAD/azure-activedirectory-library-for-python-priv/issues/20') # TODO TODO: setupExpectedClientAssertionTokenRequestResponse, updateSelfSignedJwtStubs @httpretty.activate def test_cert_happy_path(self): ''' TODO: Test Failing as of 2015/06/03 and needs to be completed. ''' self.fail("Not Yet Impelemented. Add Helper Functions and setup method") saveProto = updateSelfSignedJwtStubs() responseOptions = { noRefresh : true } response = util.create_response(responseOptions) tokenRequest = util.setupExpectedClientAssertionTokenRequestResponse(200, response.wireResponse, cp['authorityTenant']) context = adal.AuthenticationContext(cp['authorityTenant']) context.acquire_token_with_client_certificate(response.resource, cp['clientId'], cp['cert'], cp['certHash']) resetSelfSignedJwtStubs(saveProto) self.assertTrue(util.is_match_token_response(response.cachedResponse, token_response), 'The response did not match what was expected') def test_cert_bad_cert(self): cert = 'gobbledy' context = adal.AuthenticationContext(cp['authorityTenant']) with six.assertRaisesRegex(self, Exception, "Error:Invalid Certificate: Expected Start of Certificate to be '-----BEGIN RSA PRIVATE KEY-----'"): context.acquire_token_with_client_certificate(cp['resource'], cp['clientId'], cert, cp['certHash']) def test_cert_bad_thumbprint(self): thumbprint = 'gobbledy' context = adal.AuthenticationContext(cp['authorityTenant']) with six.assertRaisesRegex(self, Exception, 'thumbprint does not match a known format'): context.acquire_token_with_client_certificate( cp['resource'], cp['clientId'], cp['cert'], thumbprint) if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_e2e_examples.py000066400000000000000000000140271403263465100267460ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 import base64 import json import adal try: from tests.config import ACQUIRE_TOKEN_WITH_USERNAME_PASSWORD as user_pass_params from tests.config import ACQUIRE_TOKEN_WITH_CLIENT_CREDENTIALS as client_cred_params #per http://stackoverflow.com/questions/12487532/how-do-i-skip-a-whole-python-unittest-module-at-run-time class TestE2EExamples(unittest.TestCase): def setUp(self): self.assertIsNotNone(user_pass_params['password'], "This test cannot work without you adding a password") return super(TestE2EExamples, self).setUp() def test_acquire_token_with_user_pass_explicit(self): resource = '00000002-0000-0000-c000-000000000000' client_id_xplat = '04b07795-8ddb-461a-bbee-02f9e1bf7b46' authority = user_pass_params['authorityHostUrl'] + '/' + user_pass_params['tenant'] context = adal.AuthenticationContext(authority) token_response = context.acquire_token_with_username_password( resource, user_pass_params['username'], user_pass_params['password'], client_id_xplat) self.validate_token_response_username_password(token_response) def test_acquire_token_with_client_creds(self): resource = '00000002-0000-0000-c000-000000000000' context = adal.AuthenticationContext(client_cred_params['authority']) token_response = context.acquire_token_with_client_credentials( resource, client_cred_params['clientId'], client_cred_params['secret']) self.validate_token_response_client_credentials(token_response) @unittest.skip('https://github.com/AzureAD/azure-activedirectory-library-for-python-priv/issues/46') def test_acquire_token_with_authorization_code(self): self.fail("Not Yet Implemented") def test_acquire_token_with_refresh_token(self): authority = user_pass_params['authorityHostUrl'] + '/' + user_pass_params['tenant'] resource = '00000002-0000-0000-c000-000000000000' client_id_xplat = '04b07795-8ddb-461a-bbee-02f9e1bf7b46' # Get token using username password first context = adal.AuthenticationContext(authority) token_response = context.acquire_token_with_username_password( resource, user_pass_params['username'], user_pass_params['password'], client_id_xplat) self.validate_token_response_username_password(token_response) # Use returned refresh token to acquire a new token. refresh_token = token_response['refreshToken'] context = adal.AuthenticationContext(authority) token_response2 = context.acquire_token_with_refresh_token(refresh_token, client_id_xplat, resource) self.validate_token_response_refresh_token(token_response2) @unittest.skip('https://github.com/AzureAD/azure-activedirectory-library-for-python-priv/issues/47') def test_acquire_token_with_client_certificate(self): self.fail("Not Yet Implemented") # Validation Methods def validate_keys_in_dict(self, dict, keys): for i in keys: self.assertIn(i, dict) def validate_token_response_username_password(self, token_response): self.validate_keys_in_dict( token_response, [ 'accessToken', 'expiresIn', 'expiresOn', 'familyName', 'givenName', 'refreshToken', 'resource', 'tenantId', 'tokenType', ] ) def validate_token_response_client_credentials(self, token_response): self.validate_keys_in_dict( token_response, ['accessToken', 'expiresIn', 'expiresOn', 'resource', 'tokenType'] ) @unittest.skip('https://github.com/AzureAD/azure-activedirectory-library-for-python-priv/issues/46') def validate_token_response_authorization_code(self, token_response): self.fail("Not Yet Implemented") def validate_token_response_refresh_token(self, token_response): self.validate_keys_in_dict( token_response, [ 'accessToken', 'expiresIn', 'expiresOn', 'refreshToken', 'resource', 'tokenType' ] ) @unittest.skip('https://github.com/AzureAD/azure-activedirectory-library-for-python-priv/issues/47') def validate_token_response_client_certificate(self, token_response): self.fail("Not Yet Implemented") except: print ("WARNING: E2E example testing were skipped, for missing 'config.py'.") if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_log.py000066400000000000000000000076411403263465100251620ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 json import logging import unittest try: from cStringIO import StringIO except ImportError: from io import StringIO from adal import log as adal_logging from tests import util from tests.util import parameters as cp class TestLog(unittest.TestCase): def test_settings_none(self): current_options = adal_logging.get_logging_options() adal_logging.set_logging_options() options = adal_logging.get_logging_options() adal_logging.set_logging_options(current_options) noOptions = len(options) == 1 and options['level'] == 'ERROR' self.assertTrue(noOptions, 'Did not expect to find any logging options set: ' + json.dumps(options)) def test_console_settings(self): currentOptions = adal_logging.get_logging_options() util.turn_on_logging() options = adal_logging.get_logging_options() level = options['level'] # Set the looging options back to what they were before this test so that # future tests are logged as they should be. adal_logging.set_logging_options(currentOptions) self.assertEqual(level, 'DEBUG', 'Logging level was not the expected value of LOGGING_LEVEL.DEBUG: {}'.format(level)) def test_logging(self): log_capture_string = StringIO() handler = logging.StreamHandler(log_capture_string) util.turn_on_logging(handler=handler) test_logger = adal_logging.Logger("TokenRequest", {'correlation_id':'12345'}) test_logger.warn('a warning', log_stack_trace=True) log_contents = log_capture_string.getvalue() logging.getLogger(adal_logging.ADAL_LOGGER_NAME).removeHandler(handler) self.assertTrue('12345 - TokenRequest:a warning' in log_contents and 'Stack:' in log_contents) def test_scrub_pii(self): not_pii = "not pii" pii = "pii@contoso.com" content_with_pii = {"message": not_pii, "email": pii} expected = {"message": not_pii, "email": "..."} self.assertEqual(adal_logging.scrub_pii(content_with_pii), expected) log_capture_string = StringIO() handler = logging.StreamHandler(log_capture_string) util.turn_on_logging(handler=handler) test_logger = adal_logging.Logger("TokenRequest", {'correlation_id':'12345'}) test_logger.warn('%(message)s for user email %(email)s', content_with_pii) log_contents = log_capture_string.getvalue() logging.getLogger(adal_logging.ADAL_LOGGER_NAME).removeHandler(handler) self.assertTrue(not_pii in log_contents and pii not in log_contents) if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_mex.py000066400000000000000000000066011403263465100251650ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 os import unittest import httpretty from tests import util from adal.mex import Mex cp = util.parameters class Test_Mex(unittest.TestCase): def test_happy_path_1(self): self._happyPathTest('microsoft.mex.xml', 'https://corp.sts.microsoft.com/adfs/services/trust/13/usernamemixed') def test_happy_path_2(self): self._happyPathTest('arupela.mex.xml', 'https://fs.arupela.com/adfs/services/trust/13/usernamemixed') def test_happy_path_3(self): self._happyPathTest('archan.us.mex.xml', 'https://arvmserver2012.archan.us/adfs/services/trust/13/usernamemixed') @httpretty.activate def test_failed_request(self): httpretty.register_uri(httpretty.GET, cp['adfsMex'], status = 500) mex = Mex(cp['callContext'], cp['adfsMex']) try: mex.discover() self.fail('No exception was thrown caused by failed request') except Exception as exp: self.assertEqual(exp.args[0], 'Mex Get request returned http error: 500 and server response: HTTPretty :)') @httpretty.activate def _happyPathTest(self, file_name, expectedUrl): mexDocPath = os.path.join(os.getcwd(), 'tests', 'mex', file_name) mexFile = open(mexDocPath) mexDoc = mexFile.read() mexFile.close() httpretty.register_uri(httpretty.GET, cp['adfsMex'], body = mexDoc, status = 200) mex = Mex(cp['callContext'], cp['adfsMex']) mex.discover() url = mex.username_password_policy['url'] self.assertEqual(url, expectedUrl, 'returned url did not match: {}:{}'.format(expectedUrl, url)) @httpretty.activate def _badMexDocTest(self, file_name): mexDocPath = os.path.join(os.getcwd(), 'tests', 'mex', file_name) mexFile = open(mexDocPath) mexDoc = mexFile.read() mexFile.close() httpretty.register_uri(httpretty.GET, cp['adfsMex'], body = mexDoc, status = 200) mex = Mex(cp['callContext'], cp['adfsMex']) with self.assertRaises(Exception): mex.discover() if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_refresh_token.py000066400000000000000000000101561403263465100272320ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 sys import requests import httpretty import json try: import unittest2 as unittest except ImportError: import unittest try: from unittest import mock except ImportError: import mock import adal from adal.authentication_context import AuthenticationContext, TokenCache from tests import util from tests.util import parameters as cp class TestRefreshToken(unittest.TestCase): @httpretty.activate def test_happy_path_with_resource_client_secret(self): 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 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(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() azure-activedirectory-library-for-python-1.2.7/tests/test_self_signed_jwt.py000066400000000000000000000117571403263465100275520ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 sys import requests import httpretty import json from datetime import datetime try: import unittest2 as unittest except ImportError: import unittest try: from unittest import mock except ImportError: import mock import adal from adal.authority import Authority from adal import self_signed_jwt from adal.self_signed_jwt import SelfSignedJwt from adal.authentication_context import AuthenticationContext from tests import util from tests.util import parameters as cp class TestSelfSignedJwt(unittest.TestCase): testNowDate = cp['nowDate'] testJwtId = cp['jwtId'] expectedJwtWithThumbprint = cp['expectedJwtWithThumbprint'] expectedJwtWithPublicCert = cp['expectedJwtWithPublicCert'] unexpectedJwt = 'unexpectedJwt' testAuthority = Authority('https://login.microsoftonline.com/naturalcauses.com', False) testClientId = 'd6835713-b745-48d1-bb62-7a8248477d35' testCert = cp['cert'] testPublicCert=cp['publicCert'] def _create_jwt(self, cert, thumbprint, public_certificate = None, encodeError = None): ssjwt = SelfSignedJwt(cp['callContext'], self.testAuthority, self.testClientId) self_signed_jwt._get_date_now = mock.MagicMock(return_value = self.testNowDate) self_signed_jwt._get_new_jwt_id = mock.MagicMock(return_value = self.testJwtId) if encodeError: self_signed_jwt._encode_jwt = mock.MagicMock(return_value = self.unexpectedJwt) else: expected = self.expectedJwtWithPublicCert if public_certificate else self.expectedJwtWithThumbprint self_signed_jwt._encode_jwt = mock.MagicMock(return_value = expected) jwt = ssjwt.create(cert, thumbprint, public_certificate=public_certificate) return jwt def _create_jwt_and_match_expected_err(self, testCert, thumbprint, encodeError = None): with self.assertRaises(Exception): self._create_jwt(testCert, thumbprint, encodeError = encodeError) def _create_jwt_and_match_expected_jwt(self, cert, thumbprint): jwt = self._create_jwt(cert, thumbprint) self.assertTrue(jwt, 'No JWT generated') self.assertTrue(jwt == self.expectedJwtWithThumbprint, 'Generated JWT does not match expected:{}'.format(jwt)) def test_jwt_hash_with_public_cert(self): jwt = self._create_jwt(self.testCert, cp['certHash'], public_certificate = self.testPublicCert) self.assertTrue(jwt == self.expectedJwtWithPublicCert, 'Generated JWT does not match expected:{}'.format(jwt)) def test_create_jwt_hash_colons(self): self._create_jwt_and_match_expected_jwt(self.testCert, cp['certHash']) def test_create_jwt_hash_spaces(self): thumbprint = cp['certHash'].replace(':', ' ') self._create_jwt_and_match_expected_jwt(self.testCert, thumbprint) def test_create_jwt_hash_straight_hex(self): thumbprint = cp['certHash'].replace(':', '') self._create_jwt_and_match_expected_jwt(self.testCert, thumbprint) def test_create_jwt_invalid_cert(self): self._create_jwt_and_match_expected_err('foobar', cp['certHash'], encodeError = True) def test_create_jwt_invalid_thumbprint_1(self): self._create_jwt_and_match_expected_err(self.testCert, 'zzzz') def test_create_jwt_invalid_thumbprint_wrong_size(self): thumbprint = 'C1:5D:EA:86:56:AD:DF:67:BE:80:31:D8:5E:BD:DC:5A:D6:C4:36:E7:AA' self._create_jwt_and_match_expected_err(self.testCert, thumbprint) def test_create_jwt_invalid_thumbprint_invalid_char(self): thumbprint = 'C1:5D:EA:86:56:AD:DF:67:BE:80:31:D8:5E:BD:DC:5A:D6:C4:36:Ez' self._create_jwt_and_match_expected_err(self.testCert, thumbprint) if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_user_realm.py000066400000000000000000000146161403263465100265370ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 sys import requests import httpretty try: import unittest2 as unittest except ImportError: import unittest try: from unittest import mock except ImportError: import mock try: from urllib.parse import urlparse, quote except ImportError: from urlparse import urlparse from urllib import quote import adal from tests import util from tests.util import parameters as cp class TestUserRealm(unittest.TestCase): def setUp(self): self.authority = 'https://login.microsoftonline.com' self.user = 'test@federatedtenant-com' user_realm_path = cp['userRealmPathTemplate'].replace('', quote(self.user, safe='~()*!.\'')) query = 'api-version=1.0' self.testUrl = self.authority + user_realm_path + '?' + query return super(TestUserRealm, self).setUp() @httpretty.activate def test_happy_path_federated(self): user_realm_response = '{\"account_type\":\"Federated\",\"federation_protocol\":\"wstrust\",\"federation_metadata_url\":\"https://adfs.federatedtenant.com/adfs/services/trust/mex\",\"federation_active_auth_url\":\"https://adfs.federatedtenant.com/adfs/services/trust/2005/usernamemixed\",\"ver\":\"0.8\"}' httpretty.register_uri(httpretty.GET, uri=self.testUrl, body=user_realm_response, status=200) user_realm = adal.user_realm.UserRealm(cp['callContext'], self.user, self.authority) try: user_realm.discover() self.assertEqual(user_realm.federation_metadata_url, 'https://adfs.federatedtenant.com/adfs/services/trust/mex', 'Returned Mex URL does not match expected value: {0}'.format(user_realm.federation_metadata_url)) self.assertAlmostEqual(user_realm.federation_active_auth_url, 'https://adfs.federatedtenant.com/adfs/services/trust/2005/usernamemixed', 'Returned active auth URL does not match expected value: {0}'.format(user_realm.federation_active_auth_url)) except Exception as exp: self.assertIsNone(exp, "Error raised during function: {0}".format(exp)) util.match_standard_request_headers(httpretty.last_request()) @httpretty.activate def test_negative_wrong_field(self): user_realm_response = '{\"account_type\":\"Manageddf\",\"federation_protocol\":\"SAML20fgfg\",\"federation_metadata\":\"https://adfs.federatedtenant.com/adfs/services/trust/mex\",\"federation_active_auth_url\":\"https://adfs.federatedtenant.com/adfs/services/trust/2005/usernamemixed\",\"version\":\"0.8\"}' httpretty.register_uri(httpretty.GET, uri=self.testUrl, body=user_realm_response, status=200) user_realm = adal.user_realm.UserRealm(cp['callContext'], self.user, self.authority) try: user_realm.discover() except Exception as exp: receivedException = True pass finally: self.assertTrue(receivedException,'Did not receive expected error') util.match_standard_request_headers(httpretty.last_request()) @httpretty.activate def test_negative_no_root(self): user_realm_response = 'noroot' httpretty.register_uri(httpretty.GET, uri=self.testUrl, body=user_realm_response, status=200) user_realm = adal.user_realm.UserRealm(cp['callContext'], self.user, self.authority) try: user_realm.discover() except Exception as exp: receivedException = True pass finally: self.assertTrue(receivedException,'Did not receive expected error') util.match_standard_request_headers(httpretty.last_request()) @httpretty.activate def test_negative_empty_json(self): user_realm_response = '{}' httpretty.register_uri(httpretty.GET, uri=self.testUrl, body=user_realm_response, status=200) user_realm = adal.user_realm.UserRealm(cp['callContext'], self.user, self.authority) try: user_realm.discover() except Exception as exp: receivedException = True pass finally: self.assertTrue(receivedException,'Did not receive expected error') util.match_standard_request_headers(httpretty.last_request()) @httpretty.activate def test_negative_fed_err(self): user_realm_response = '{\"account_type\":\"Federated\",\"federation_protocol\":\"wstrustww\",\"federation_metadata_url\":\"https://adfs.federatedtenant.com/adfs/services/trust/mex\",\"federation_active_auth_url\":\"https://adfs.federatedtenant.com/adfs/services/trust/2005/usernamemixed\",\"ver\":\"0.8\"}' httpretty.register_uri(httpretty.GET, uri=self.testUrl, body=user_realm_response, status=200) user_realm = adal.user_realm.UserRealm(cp['callContext'], self.user, self.authority) try: user_realm.discover() except Exception as exp: receivedException = True pass finally: self.assertTrue(receivedException,'Did not receive expected error') util.match_standard_request_headers(httpretty.last_request()) if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_username_password.py000066400000000000000000001044221403263465100301350ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 logging import json try: from StringIO import StringIO except ImportError: from io import StringIO import sys import requests import httpretty from adal import oauth2_client try: import unittest2 as unittest except ImportError: import unittest try: from unittest import mock except ImportError: import mock from tests import util from tests.util import parameters as cp import adal from adal.authentication_context import AuthenticationContext from adal.mex import Mex from adal.token_request import TokenRequest from adal.oauth2_client import OAuth2Client from adal.user_realm import UserRealm from adal.wstrust_response import WSTrustResponse from adal.wstrust_request import WSTrustRequest from adal import log from adal.authority import Authority from adal.constants import AADConstants try: from urllib.parse import urlparse, urlencode except ImportError: from urllib import urlencode from urlparse import urlparse class TestUsernamePassword(unittest.TestCase): def setUp(self): util.reset_logging() util.clear_static_cache() def tearDown(self): util.reset_logging() util.clear_static_cache() def setup_expected_oauth_assertion_request(self, response): with open(cp['AssertionFile']) as assertFile: assertion = assertFile.read() queryParameters = {} queryParameters['grant_type'] = 'urn:ietf:params:oauth:grant-type:saml1_1-bearer' queryParameters['client_id'] = response['clientId'] queryParameters['resource'] = response['resource'] queryParameters['assertion'] = assertion queryParameters['scope'] = 'openid' return util.setup_expected_oauth_response(queryParameters, cp['tokenUrlPath'], 200, response['wireResponse'], cp['authority']) def setup_expected_username_password_request_response(self, httpCode, returnDoc, authorityEndpoint, isAdfs = False): queryParameters = {} queryParameters['grant_type'] = 'password' queryParameters['client_id'] = cp['clientId'] queryParameters['resource'] = cp['resource'] queryParameters['username'] = cp['username'] queryParameters['password'] = cp['password'] queryParameters['scope'] = 'openid' query = urlencode(queryParameters) token_path = cp['tokenPath'] if isAdfs: token_path = cp['tokenPath'] # TODO: figure out to match w/o query + cp['extraQP'] url = '{}{}'.format(authorityEndpoint, token_path) #'https://login.windows.net/rrandallaad1.onmicrosoft.com/oauth2/token?slice=testslice&api-version=1.0' httpretty.register_uri(httpretty.POST, url, json.dumps(returnDoc), status = httpCode, content_type = 'text/json') @httpretty.activate def test_happy_path_adfs_authority(self): adfsAuthority = 'https://contoso.com/adfs' responseOptions = { 'authority' : adfsAuthority, 'mrrt' : True } response = util.create_response(responseOptions) upRequest = self.setup_expected_username_password_request_response(200, response['wireResponse'], adfsAuthority, True) context = adal.AuthenticationContext(adfsAuthority, False) #action token_response = context.acquire_token_with_username_password(response['resource'], cp['username'], cp['password'], cp['clientId']) #assert self.assertTrue(util.is_match_token_response(response['cachedResponse'], token_response), 'Response did not match expected: ' + json.dumps(token_response)) @httpretty.activate def test_managed_happy_path(self): util.setup_expected_user_realm_response_common(False) response = util.create_response() authorityUrl = response['authority'] upRequest = self.setup_expected_username_password_request_response(200, response['wireResponse'], authorityUrl) context = adal.AuthenticationContext(authorityUrl) #action token_response = context.acquire_token_with_username_password(response['resource'], cp['username'], cp['password'], cp['clientId']) #assert self.assertTrue(util.is_match_token_response(response['cachedResponse'], token_response), 'Response did not match expected: ' + json.dumps(token_response)) # Since this test is the most code intensive it will make a good test case for # correlation id. @httpretty.activate def test_federated_happy_path_and_correlation_id(self): util.setup_expected_user_realm_response_common(True) util.setup_expected_mex_wstrust_request_common() response = util.create_response() assertion = self.setup_expected_oauth_assertion_request(response) buffer = StringIO() handler = logging.StreamHandler(buffer) util.turn_on_logging(level='DEBUG', handler=handler) authorityUrl = response['authority'] context = adal.AuthenticationContext(authorityUrl) correlation_id = '12300002-0000-0000-c000-000000000000' context.correlation_id = correlation_id #action token_response = context.acquire_token_with_username_password(response['resource'], cp['username'], cp['password'], cp['clientId']) self.assertTrue(util.is_match_token_response(response['cachedResponse'], token_response), 'Response did not match expected: ' + json.dumps(token_response)) #assert log_content = buffer.getvalue() self.assertTrue(correlation_id in log_content, 'Logging was turned on but no messages were recieved.') self.assertNotIn(cp['clientId'], log_content, "Should not log ClientID") self.assertNotIn( cp['username'].split('@')[0], log_content, "Should not contain PII") @httpretty.activate def test_invalid_id_token(self): util.setup_expected_user_realm_response_common(False) response = util.create_response() wireResponse = response['wireResponse'] response_options = { 'noIdToken' : True } response = util.create_response(response_options) # break the id token idToken = wireResponse['id_token'] idToken = idToken.replace('.', ' ') wireResponse['id_token'] = idToken authorityUrl = response['authority'] upRequest = self.setup_expected_username_password_request_response(200, wireResponse, authorityUrl) context = adal.AuthenticationContext(authorityUrl) #action token_response = context.acquire_token_with_username_password(response['resource'], cp['username'], cp['password'], cp['clientId']) #assert self.assertTrue(util.is_match_token_response(response['cachedResponse'], token_response), 'Response did not match expected: ' + json.dumps(token_response)) def create_mex_stub(self, usernamePasswordUrl, err=None): mex = Mex(cp['callContext'], '') if err: mex.discover = mock.MagicMock(side_effect=err)#TODO: verify the mock gets called else: mex.username_password_policy = {'url' : usernamePasswordUrl} return mex def create_user_realm_stub(self, protocol, accountType, mexUrl, wstrustUrl, err=None): userRealm = UserRealm(cp['callContext'], '', '') userRealm.discover = mock.MagicMock()#TODO: verify the mock gets called userRealm.federation_protocol = protocol userRealm.account_type = accountType userRealm.federation_metadata_url = mexUrl userRealm.federation_active_auth_url = wstrustUrl return userRealm def create_wstrust_request_stub(self, err, tokenType, noToken=None): wstrust_response = WSTrustResponse(cp['callContext'], '', '') wstrust_response.error_code = err wstrust_response.parse = mock.MagicMock() if not noToken: wstrust_response.token = b'This is a stubbed token' wstrust_response.token_type = tokenType wstrust_request = WSTrustRequest(cp['callContext'], '', '', '') def side_effect (username, password): if err: raise err return wstrust_response wstrust_request.acquire_token = mock.MagicMock(side_effect=side_effect) return wstrust_request def create_authentication_context_stub(self, authority): context = AuthenticationContext(authority, False) context.authority.token_endpoint = authority + cp['tokenPath'] return context def create_oauth2_client_stub(self, authority, token_response, err): authorityObject = Authority(authority, False) authorityObject.token_endpoint = AADConstants.TOKEN_ENDPOINT_PATH authorityObject.device_code_endpoint = AADConstants.DEVICE_ENDPOINT_PATH client = OAuth2Client(cp['callContext'], authorityObject) def side_effect (oauth): return token_response client.get_token = mock.MagicMock(side_effect=side_effect) return client def stub_out_token_request_dependencies(self, tokenRequest, userRealm, mex, wstrustRequest=None, oauthClient=None): tokenRequest._create_user_realm_request = mock.MagicMock(return_value=userRealm) tokenRequest._create_mex = mock.MagicMock(return_value=mex) tokenRequest._create_wstrust_request = mock.MagicMock(return_value=wstrustRequest) tokenRequest._create_oauth2_client = mock.MagicMock(return_value=oauthClient) def test_federated_failed_mex(self): context = self.create_authentication_context_stub(cp['authorityTenant']) mex = self.create_mex_stub(cp['adfsWsTrust'], Exception('mex failed')) userRealm = self.create_user_realm_stub('wstrust', 'federated', cp['adfsMex'], cp['adfsWsTrust']) wstrustRequest = self.create_wstrust_request_stub(None, 'urn:oasis:names:tc:SAML:1.0:assertion') response = util.create_response() oauthClient = self.create_oauth2_client_stub(cp['authority'], response['cachedResponse'], None) tokenRequest = TokenRequest(cp['callContext'], context, response['clientId'], response['resource']) self.stub_out_token_request_dependencies(tokenRequest, userRealm, mex, wstrustRequest, oauthClient) #action token_response = tokenRequest.get_token_with_username_password(cp['username'], cp['password']) #assert self.assertTrue(util.is_match_token_response(response['cachedResponse'], token_response), 'The response did not match what was expected') def test_federated_user_realm_returns_no_mex_endpoint_wstrust13(self): context = self.create_authentication_context_stub(cp['authorityTenant']) mex = self.create_mex_stub(cp['adfsWsTrust']) userRealm = self.create_user_realm_stub('wstrust', 'federated', None, cp['adfsWsTrust']) wstrustRequest = self.create_wstrust_request_stub(None, 'urn:oasis:names:tc:SAML:1.0:assertion') response = util.create_response() oauthClient = self.create_oauth2_client_stub(cp['authority'], response['decodedResponse'], None) tokenRequest = TokenRequest(cp['callContext'], context, response['clientId'], response['resource']) self.stub_out_token_request_dependencies(tokenRequest, userRealm, mex, wstrustRequest, oauthClient) #action token_response = tokenRequest.get_token_with_username_password(cp['username'], cp['password']) #assert self.assertTrue(util.is_match_token_response(response['cachedResponse'], token_response), 'The response did not match what was expected') def test_federated_user_realm_returns_no_mex_endpoint_wstrust2005(self): context = self.create_authentication_context_stub(cp['authorityTenant']) mex = self.create_mex_stub(cp['adfsWsTrust2005']) userRealm = self.create_user_realm_stub('wstrust', 'federated', None, cp['adfsWsTrust2005']) wstrustRequest = self.create_wstrust_request_stub(None, 'urn:oasis:names:tc:SAML:1.0:assertion') response = util.create_response() oauthClient = self.create_oauth2_client_stub(cp['authority'], response['decodedResponse'], None) tokenRequest = TokenRequest(cp['callContext'], context, response['clientId'], response['resource']) self.stub_out_token_request_dependencies(tokenRequest, userRealm, mex, wstrustRequest, oauthClient); #action token_response = tokenRequest.get_token_with_username_password(cp['username'], cp['password']) #assert self.assertTrue(util.is_match_token_response(response['cachedResponse'], token_response), 'The response did not match what was expected') def test_user_realm_returns_unknown_account_type(self): context = self.create_authentication_context_stub(cp['authorityTenant']) mex = self.create_mex_stub(cp['adfsWsTrust']) userRealm = self.create_user_realm_stub('wstrust', 'unknown', cp['adfsMex'], cp['adfsWsTrust']) tokenRequest = TokenRequest(cp['callContext'], context, cp['clientId'], cp['resource']) self.stub_out_token_request_dependencies(tokenRequest, userRealm, mex) #action try: tokenRequest.get_token_with_username_password(cp['username'], cp['password']) self.fail('Exception not raised, when it should have been') except Exception as err: #assert self.assertTrue(err, 'Did not receive expected err.') self.assertTrue('unknown AccountType' in err.args[0], 'Did not receive expected error message.') def test_federated_saml2(self): context = self.create_authentication_context_stub(cp['authorityTenant']) mex = self.create_mex_stub(cp['adfsWsTrust']) userRealm = self.create_user_realm_stub('wstrust', 'federated', cp['adfsMex'], cp['adfsWsTrust']) wstrustRequest = self.create_wstrust_request_stub(None, 'urn:oasis:names:tc:SAML:2.0:assertion') response = util.create_response() oauthClient = self.create_oauth2_client_stub(cp['authority'], response['cachedResponse'], None) tokenRequest = TokenRequest(cp['callContext'], context, response['clientId'], response['resource']) self.stub_out_token_request_dependencies(tokenRequest, userRealm, mex, wstrustRequest, oauthClient) #action token_response = tokenRequest.get_token_with_username_password(cp['username'], cp['password']) #assert self.assertTrue(util.is_match_token_response(response['cachedResponse'], token_response), 'The response did not match what was expected') def test_federated_unknown_token_type(self): context = self.create_authentication_context_stub(cp['authorityTenant']) mex = self.create_mex_stub(cp['adfsWsTrust']) userRealm = self.create_user_realm_stub('wstrust', 'federated', cp['adfsMex'], cp['adfsWsTrust']) wstrustRequest = self.create_wstrust_request_stub(None, 'urn:oasis:names:tc:SAML:100.0:assertion') response = util.create_response() oauthClient = self.create_oauth2_client_stub(cp['authority'], response['decodedResponse'], None) tokenRequest = TokenRequest(cp['callContext'], context, response['clientId'], response['resource']) self.stub_out_token_request_dependencies(tokenRequest, userRealm, mex, wstrustRequest, oauthClient) #action try: tokenRequest.get_token_with_username_password(cp['username'], cp['password']) self.assertTrue(receivedException, 'Did not receive expected error') except Exception as err: #assert self.assertTrue('token type' in err.args[0], "Error message did not contain 'token type'. message:{}".format(err.args[0])) def test_federated_failed_wstrust(self): context = self.create_authentication_context_stub(cp['authorityTenant']) mex = self.create_mex_stub(cp['adfsWsTrust']) userRealm = self.create_user_realm_stub('wstrust', 'federated', None, cp['adfsWsTrust']) mock_err_msg = 'Network not available' wstrustRequest = self.create_wstrust_request_stub(Exception(mock_err_msg), 'urn:oasis:names:tc:SAML:1.0:assertion') response = util.create_response() oauthClient = self.create_oauth2_client_stub(cp['authority'], response['cachedResponse'], None) tokenRequest = TokenRequest(cp['callContext'], context, response['clientId'], response['resource']) self.stub_out_token_request_dependencies(tokenRequest, userRealm, mex, wstrustRequest, oauthClient) #action try: tokenRequest.get_token_with_username_password(cp['username'], cp['password']) self.fail('Did not receive expected error') except Exception as exp: #assert self.assertEqual(mock_err_msg, exp.args[0]) def test_federated_wstrust_unparseable(self): context = self.create_authentication_context_stub(cp['authorityTenant']) mex = self.create_mex_stub(cp['adfsWsTrust']) userRealm = self.create_user_realm_stub('wstrust', 'federated', None, cp['adfsWsTrust']) wstrustRequest = self.create_wstrust_request_stub(None, 'urn:oasis:names:tc:SAML:2.0:assertion', True) response = util.create_response() oauthClient = self.create_oauth2_client_stub(cp['authority'], response['decodedResponse'], None) tokenRequest = TokenRequest(cp['callContext'], context, response['clientId'], response['resource']) self.stub_out_token_request_dependencies(tokenRequest, userRealm, mex, wstrustRequest, oauthClient) #action try: tokenRequest.get_token_with_username_password(cp['username'], cp['password']) self.fail('Did not receive expected error') except Exception as exp: #assert self.assertEqual('Unsuccessful RSTR.\n\terror code: None\n\tfaultMessage: None', exp.args[0]) def test_federated_wstrust_unknown_token_type(self): context = self.create_authentication_context_stub(cp['authorityTenant']) mex = self.create_mex_stub(cp['adfsWsTrust']) userRealm = self.create_user_realm_stub('wstrust', 'federated', None, cp['adfsWsTrust']) wstrustRequest = self.create_wstrust_request_stub(None, 'urn:oasis:names:tc:SAML:100.0:assertion', True) response = util.create_response() oauthClient = self.create_oauth2_client_stub(cp['authority'], response['decodedResponse'], None) tokenRequest = TokenRequest(cp['callContext'], context, response['clientId'], response['resource']) self.stub_out_token_request_dependencies(tokenRequest, userRealm, mex, wstrustRequest, oauthClient) #action try: tokenRequest.get_token_with_username_password(cp['username'], cp['password']) self.fail(receivedException, 'Did not receive expected error') except Exception as exp: #assert self.assertEqual('Unsuccessful RSTR.\n\terror code: None\n\tfaultMessage: None', exp.args[0]) def test_parse_id_token_with_unicode(self): client = self.create_oauth2_client_stub('https://foo/ba.onmicrosoft.com', None, None) encoded_token = '.eyJmYW1pbHlfbmFtZSI6Ikd1aW5lYmVydGnDqHJlIn0.'# JWT with payload = {"family_name": "Guinebertière"} result = client._parse_id_token(encoded_token) self.assertEqual(result['familyName'], u'Guinebertière') def test_jwt_cracking(self): testData = [ [ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9.', { 'header' : 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0', 'JWSPayload' : 'eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9', 'JWSSig' : '' } ], # remove header [ '.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9.', { 'header' : '', 'JWSPayload' : 'eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9', 'JWSSig' : '' } ], # Add JWSSig [ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9.foobar', { 'header' : 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0', 'JWSPayload' : 'eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9', 'JWSSig' : 'foobar' } ], # Remove JWS payload [ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0..', None ], # Remove JWS payload [ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0..foobar', None ], # JWT payload is only a space. [ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0. .foobar', None ], # Add space [ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1 mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9.', None ], # remove first period. [ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9.', None ], # remove second period. [ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9', None ], # prefixed space [ ' eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9.foobar', None ], # trailing space [ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9.foobar ', None ], # add section [ 'notsupposedtobehere.eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9.foobar', None ], # extra stuff at beginning seperated by space. [ 'stuff eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9.foobar', None ], ] client = self.create_oauth2_client_stub('https://foo/ba.onmicrosoft.com', None, None) for testCase in testData: testJWT = testCase[0] testResult = testCase[1] crackedJwt = client._open_jwt(testJWT) if testResult: resp = util.dicts_equal(testResult, crackedJwt) self.assertTrue(resp is None, 'The cracked token does not match the expected result.: {}'.format(resp)) else: self.assertFalse(crackedJwt) @httpretty.activate def test_bad_int_in_response(self): util.setup_expected_user_realm_response_common(False) response = util.create_response() response['wireResponse']['expires_in'] = 'foo' upRequest = self.setup_expected_username_password_request_response(200, response['wireResponse'], response['authority']) authorityUrl = response['authority'] context = adal.AuthenticationContext(authorityUrl) #action try: token_response = context.acquire_token_with_username_password(response['resource'], cp['username'], cp['password'], cp['clientId']) self.fail('Did not see expected warning message about bad expires_in field') except Exception as exp: #assert self.assertEqual("invalid literal for int() with base 10: 'foo'", exp.args[0]) @httpretty.activate def test_bad_id_token_base64_in_response(self): foundWarning = False util.setup_expected_user_realm_response_common(False) response = util.create_response() log_content = StringIO() handler = logging.StreamHandler(log_content) util.turn_on_logging(level='WARNING', handler=handler) response['wireResponse']['id_token'] = 'aaaaaaa./+===.aaaaaa' expected_warn = 'The returned id_token could not be decoded: aaaaaaa./+===.aaaaaa' authorityUrl = response['authority'] upRequest = self.setup_expected_username_password_request_response(200, response['wireResponse'], authorityUrl) context = adal.AuthenticationContext(authorityUrl) #action and verify self.assertRaises(UnicodeDecodeError, context.acquire_token_with_username_password, response['resource'], cp['username'], cp['password'], cp['clientId']) @httpretty.activate def test_no_token_type(self): util.setup_expected_user_realm_response_common(False) response = util.create_response() authorityUrl = response['authority'] del response['wireResponse']['token_type'] upRequest = self.setup_expected_username_password_request_response(200, response['wireResponse'], response['authority']) context = adal.AuthenticationContext(authorityUrl) #action try: context.acquire_token_with_username_password(response['resource'], cp['username'], cp['password'], cp['clientId']) self.fail('Did not receive expected error about missing token_type') except Exception as exp: #assert self.assertEqual('wire_response is missing token_type', exp.args[0]) @httpretty.activate def test_no_access_token(self): util.setup_expected_user_realm_response_common(False) response = util.create_response() del response['wireResponse']['access_token'] upRequest = self.setup_expected_username_password_request_response(200, response['wireResponse'], response['authority']) authorityUrl = response['authority'] context = adal.AuthenticationContext(authorityUrl) #action try: token_response = context.acquire_token_with_username_password(response['resource'], cp['username'], cp['password'], cp['clientId']) self.fail('Did not receive expected error about missing token_type') except Exception as exp: #assert self.assertEqual('wire_response is missing access_token', exp.args[0]) if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_wstrust_request.py000066400000000000000000000074171403263465100277050ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 os import unittest import httpretty try: from unittest import mock except ImportError: import mock from adal.wstrust_request import WSTrustRequest from adal.wstrust_response import WSTrustResponse from adal.constants import WSTrustVersion TEST_CORRELATION_ID = 'test-correlation-id-123456789' wstrustEndpoint = 'https://test.wstrust.endpoint/' _call_context = { 'log_context' : {'correlation_id': TEST_CORRELATION_ID } } class Test_wstrust_request(unittest.TestCase): @httpretty.activate def test_happy_path(self): username = 'test_username' password = 'test_password' appliesTo = 'test_appliesTo' wstrustFile = open(os.path.join(os.getcwd(), 'tests', 'wstrust', 'RST.xml'), mode='r') templateRST = wstrustFile.read() rst = templateRST \ .replace('%USERNAME%', username) \ .replace('%PASSWORD%', password) \ .replace('%APPLIES_TO%', appliesTo) \ .replace('%WSTRUST_ENDPOINT%', wstrustEndpoint) #rstRequest = setupUpOutgoingRSTCompare(rst) request = WSTrustRequest(_call_context, wstrustEndpoint, appliesTo, WSTrustVersion.WSTRUST13) # TODO: handle rstr should be mocked out to prevent handling here. # TODO: setupUpOutgoingRSTCompare. Use this to get messageid, created, expires, etc comparisons. httpretty.register_uri(method=httpretty.POST, uri=wstrustEndpoint, status=200, body='') request._handle_rstr =mock.MagicMock() request.acquire_token(username, password) wstrustFile.close() @httpretty.activate def test_fail_to_parse_rstr(self): username = 'test_username' password = 'test_password' appliesTo = 'test_appliesTo' templateFile = open(os.path.join(os.getcwd(), 'tests', 'wstrust', 'RST.xml'), mode='r') templateRST = templateFile.read() templateFile.close() rst = templateRST \ .replace('%USERNAME%', username) \ .replace('%PASSWORD%', password) \ .replace('%APPLIES_TO%', appliesTo) \ .replace('%WSTRUST_ENDPOINT%', wstrustEndpoint) httpretty.register_uri(method=httpretty.POST, uri=wstrustEndpoint, status=200, body='fake response body') request = WSTrustRequest(_call_context, wstrustEndpoint, appliesTo, WSTrustVersion.WSTRUST13) with self.assertRaises(Exception): request.acquire_token(username, password) if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/test_wstrust_response.py000066400000000000000000000144471403263465100300540ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 import os import six try: from xml.etree import cElementTree as ET except ImportError: from xml.etree import ElementTree as ET from adal.constants import XmlNamespaces, Errors, WSTrustVersion from adal.wstrust_response import WSTrustResponse from adal.wstrust_response import findall_content _namespaces = XmlNamespaces.namespaces _call_context = {'log_context' : {'correlation-id':'test-corr-id'}} class Test_wstrustresponse(unittest.TestCase): def test_parse_error_happy_path(self): errorResponse = ''' http://www.w3.org/2005/08/addressing/soap/fault 2013-07-30T00:32:21.989Z 2013-07-30T00:37:21.989Z s:Sender a:RequestFailed MSIS3127: The specified request failed. ''' wstrustResponse = WSTrustResponse(_call_context, errorResponse, WSTrustVersion.WSTRUST13) exception_text = "Server returned error in RSTR - ErrorCode: RequestFailed : FaultMessage: MSIS3127: The specified request failed" with six.assertRaisesRegex(self, Exception, exception_text) as cm: wstrustResponse.parse() def test_token_parsing_happy_path(self): wstrustFile = open(os.path.join(os.getcwd(), 'tests', 'wstrust', 'RSTR.xml')) wstrustResponse = WSTrustResponse(_call_context, wstrustFile.read(), WSTrustVersion.WSTRUST13) wstrustResponse.parse() wstrustFile.close() self.assertEqual(wstrustResponse.token_type, 'urn:oasis:names:tc:SAML:1.0:assertion', 'TokenType did not match expected value: ' + wstrustResponse.token_type) attribute_values = ET.fromstring(wstrustResponse.token).findall('saml:AttributeStatement/saml:Attribute/saml:AttributeValue', _namespaces) self.assertEqual(2, len(attribute_values)) self.assertEqual('1TIu064jGEmmf+hnI+F0Jg==', attribute_values[1].text) def test_rstr_none(self): with six.assertRaisesRegex(self, Exception, 'Received empty RSTR response body.') as cm: wstrustResponse = WSTrustResponse(_call_context, None, WSTrustVersion.WSTRUST13) wstrustResponse.parse() def test_rstr_empty_string(self): with six.assertRaisesRegex(self, Exception, 'Received empty RSTR response body.') as cm: wstrustResponse = WSTrustResponse(_call_context, '', WSTrustVersion.WSTRUST13) wstrustResponse.parse() def test_rstr_unparseable_xml(self): with six.assertRaisesRegex(self, Exception, 'Failed to parse RSTR in to DOM'): wstrustResponse = WSTrustResponse(_call_context, ' foo """ sample = ('' + content + '') # Demonstrating how XML-based parser won't give you the raw content as-is element = ET.fromstring(sample).findall('{SAML:assertion}Assertion')[0] assertion_via_xml_parser = ET.tostring(element) self.assertNotEqual(content, assertion_via_xml_parser) self.assertNotIn(b"", assertion_via_xml_parser) # The findall_content() helper, based on Regex, will return content as-is. self.assertEqual([content], findall_content(sample, "Wrapper")) def test_findall_content_for_real(self): with open(os.path.join(os.getcwd(), 'tests', 'wstrust', 'RSTR.xml')) as f: rstr = f.read() wstrustResponse = WSTrustResponse(_call_context, rstr, WSTrustVersion.WSTRUST13) wstrustResponse.parse() self.assertIn("", rstr) self.assertIn(b"", wstrustResponse.token) # It is in bytes if __name__ == '__main__': unittest.main() azure-activedirectory-library-for-python-1.2.7/tests/util.py000066400000000000000000000711341403263465100243150ustar00rootroot00000000000000#------------------------------------------------------------------------------ # # 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 os import re import json import time import httpretty from datetime import datetime, timedelta import dateutil.parser try: from urllib.parse import urlencode from urllib.parse import urlparse except ImportError: from urllib import urlencode from urlparse import urlparse from adal import log _dirname = os.path.dirname(__file__) success_response = { 'access_token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1THdqcHdBSk9NOW4tQSJ9.eyJhdWQiOiIwMDAwMDAwMi0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC82MmYwMzQ3MS02N2MxLTRjNTAtYjlkMS0xMzQ1MDc5ZDk3NzQvIiwiaWF0IjoxMzc4NjAxMTY4LCJuYmYiOjEzNzg2MDExNjgsImV4cCI6MTM3ODYyOTk2OCwidmVyIjoiMS4wIiwidGlkIjoiNjJmMDM0NzEtNjdjMS00YzUwLWI5ZDEtMTM0NTA3OWQ5Nzc0Iiwib2lkIjoiZjEzMDkzNDEtZDcyMy00YTc1LTk2YzktNGIyMTMzMzk0Mjg3Iiwic3ViIjoiZjEzMDkzNDEtZDcyMy00YTc1LTk2YzktNGIyMTMzMzk0Mjg3IiwiYXBwaWQiOiI1YzI1ZDFiZi1iMjMyLTQwMzUtYjZiOS0yYjdlN2U4MzQ2ZDYiLCJhcHBpZGFjciI6IjEifQ.qXM7f9TTiLApxVMwaSrISQQ6UAnfKvKhoIlN9rB0Eff2VXvIWKGRsclPkMQ5x42BQz2N6pSXEsN-LsNCZlQ76Rc3rVRONzeCYh7q_NXcCJG_d6SJTtV5GBfgqFlgT8UF5rblabbMdOiOrddvJm048hWt2Nm3qD3QjQdPBlD7Ksn-lUR1jEJPIqDaBjGom8RawrZTW6X1cy1Kr8mEYFkxcbU91k_RZUumONep9FTR8gfPkboeD8zyvOy64UeysEtcuaNCfhHSBFcwC8MwjUr5r_T7au7ywAcYDOVgoa7oF_dN1JNweiDoNNZ9tyUS-RY3sa3-gXk77gRxpA4CkpittQ', 'token_type': 'Bearer', 'expires_in': 28800, 'resource': '00000002-0000-0000-c000-000000000000', } refresh_token = 'AwABAAAAvPM1KaPlrEqdFSBzjqfTGCDeE7YHWD9jkU2WWYKLjxu928QAbkoFyWpgJLFcp65DcbBqOSYVq5Ty_60YICIdFw61SG4-eT1nWHNOPdzsL2ZzloUsp2DpqlIr1s5Z3953oQBi7dOqiHk37NXQqmNEJ7MfmDp6w3EOa29EPARvjGIHFgtICW1-Y82npw1v1g8Ittb02pksNU2XzH2X0E3l3TuSZWsX5lpl-kfPOc8zppU6bwvT-VOPHZVVLQedDIQZyOiFst9HLUjbiIvBgV7tNwbB4H5yF56QQscz49Nrb3g0ibuNDo7efFawLzNoVHzoTrOTcCGSG1pt8Z-npByrEe7vg1o4nNFjspuxlyMGdnYRAnaZfvgzqROP_m7ZqSd6IAA' success_response_with_refresh = { 'access_token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1THdqcHdBSk9NOW4tQSJ9.eyJhdWQiOiIwMDAwMDAwMi0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC82MmYwMzQ3MS02N2MxLTRjNTAtYjlkMS0xMzQ1MDc5ZDk3NzQvIiwiaWF0IjoxMzc4NjAxMTY4LCJuYmYiOjEzNzg2MDExNjgsImV4cCI6MTM3ODYyOTk2OCwidmVyIjoiMS4wIiwidGlkIjoiNjJmMDM0NzEtNjdjMS00YzUwLWI5ZDEtMTM0NTA3OWQ5Nzc0Iiwib2lkIjoiZjEzMDkzNDEtZDcyMy00YTc1LTk2YzktNGIyMTMzMzk0Mjg3Iiwic3ViIjoiZjEzMDkzNDEtZDcyMy00YTc1LTk2YzktNGIyMTMzMzk0Mjg3IiwiYXBwaWQiOiI1YzI1ZDFiZi1iMjMyLTQwMzUtYjZiOS0yYjdlN2U4MzQ2ZDYiLCJhcHBpZGFjciI6IjEifQ.qXM7f9TTiLApxVMwaSrISQQ6UAnfKvKhoIlN9rB0Eff2VXvIWKGRsclPkMQ5x42BQz2N6pSXEsN-LsNCZlQ76Rc3rVRONzeCYh7q_NXcCJG_d6SJTtV5GBfgqFlgT8UF5rblabbMdOiOrddvJm048hWt2Nm3qD3QjQdPBlD7Ksn-lUR1jEJPIqDaBjGom8RawrZTW6X1cy1Kr8mEYFkxcbU91k_RZUumONep9FTR8gfPkboeD8zyvOy64UeysEtcuaNCfhHSBFcwC8MwjUr5r_T7au7ywAcYDOVgoa7oF_dN1JNweiDoNNZ9tyUS-RY3sa3-gXk77gRxpA4CkpittQ', 'token_type': 'Bearer', 'expires_in': 28800, 'resource': '00000002-0000-0000-c000-000000000000', 'scope' : '62e90394-69f5-4237-9190-012177145e10', 'refresh_token' : refresh_token } encoded_id_token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoicnJhbmRhbGxAcnJhbmRhbGxhYWQxLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IjRnVHY0RXRvWVctRFRvdzBiRG5KZDFBQTRzZkNoQmJqZXJtcXQ2UV9aYTQiLCJmYW1pbHlfbmFtZSI6IlJhbmRhbGwiLCJnaXZlbl9uYW1lIjoiUmljaCJ9.' parsed_id_token = { 'tenantId' : 'cceba14c-6a00-49ac-b806-84de52bf1d42', 'userId' : 'rrandall@rrandallaad1.onmicrosoft.com', 'givenName' : 'Rich', 'familyName' : 'Randall', 'isUserIdDisplayable' : True, 'oid': 'a443204a-abc9-4cb8-adc1-c0dfc12300aa' } decoded_id_token = { 'aud': 'e958c09a-ac37-4900-b4d7-fb3eeaf7338d', 'iss': 'https://sts.windows.net/cceba14c-6a00-49ac-b806-84de52bf1d42/', 'iat': 1391645458, 'nbf': 1391645458, 'exp': 1391649358, 'ver': '1.0', 'tid': 'cceba14c-6a00-49ac-b806-84de52bf1d42', 'oid': 'a443204a-abc9-4cb8-adc1-c0dfc12300aa', 'upn': 'rrandall@rrandallaad1.onmicrosoft.com', 'unique_name': 'rrandall@rrandallaad1.onmicrosoft.com', 'sub': '4gTv4EtoYW-DTow0bDnJd1AA4sfChBbjermqt6Q_Za4', 'family_name': 'Randall', 'given_name': 'Rich' } encoded_id_token_url_safe = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlOTU4YzA5YS1hYzM3LTQ5MDAtYjRkNy1mYjNlZWFmNzMzOGQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jY2ViYTE0Yy02YTAwLTQ5YWMtYjgwNi04NGRlNTJiZjFkNDIvIiwiaWF0IjoxMzkxNjQ1NDU4LCJuYmYiOjEzOTE2NDU0NTgsImV4cCI6MTM5MTY0OTM1OCwidmVyIjoiMS4wIiwidGlkIjoiY2NlYmExNGMtNmEwMC00OWFjLWI4MDYtODRkZTUyYmYxZDQyIiwib2lkIjoiYTQ0MzIwNGEtYWJjOS00Y2I4LWFkYzEtYzBkZmMxMjMwMGFhIiwidXBuIjoiZm9vYmFyQHNvbWVwbGFjZWVsc2UuY29tIiwidW5pcXVlX25hbWUiOiJycmFuZGFsbEBycmFuZGFsbGFhZDEub25taWNyb3NvZnQuY29tIiwic3ViIjoiNGdUdjRFdG9ZVy1EVG93MGJEbkpkMUFBNHNmQ2hCYmplcm1xdDZRX1phNCIsImZhbWlseV9uYW1lIjoiUmFuZGFsbCIsImdpdmVuX25hbWUiOiJSaTw_Y2gifQ==.' parsed_id_token_url_safe = { 'tenantId' : 'cceba14c-6a00-49ac-b806-84de52bf1d42', 'userId' : 'foobar@someplaceelse.com', 'givenName' : 'Ri', 'authorityHosts': { 'global': 'login.microsoftonline.com', 'china': 'login.chinacloudapi.cn', 'gov': 'login.microsoftonline.us' } } parameters['refreshToken'] = refresh_token # This is a default authority to be used in tests that don't care that there are multiple. parameters['authority'] = parameters['evoEndpoint'] parameters['authorityTenant'] = parameters['authority'] + parameters['tenant'] parameters['adfsUrlNoPath'] = 'https://adfs.federatedtenant.com' parameters['adfsMexPath'] = '/adfs/services/trust/mex' parameters['adfsWsTrustPath'] = '/adfs/services/trust/13/usernamemixed' parameters['adfsWsTrustPath2005'] = '/adfs/services/trust/2005/usernamemixed' parameters['adfsMex'] = parameters['adfsUrlNoPath'] + parameters['adfsMexPath'] parameters['adfsWsTrust'] = parameters['adfsUrlNoPath'] + parameters['adfsWsTrustPath'] parameters['adfsWsTrust2005'] = parameters['adfsUrlNoPath'] + parameters['adfsWsTrustPath2005'] parameters['successResponse'] = success_response parameters['successResponseWithRefresh'] = success_response_with_refresh parameters['authUrlResult'] = urlparse(parameters['evoEndpoint'] + parameters['tenant']) parameters['authUrl'] = parameters['authUrlResult'].geturl() parameters['tokenPath'] = '/oauth2/token' parameters['extraQP'] = '?api-version=1.0' parameters['tokenUrlPath'] = parameters['authUrlResult'].path + parameters['tokenPath'] + parameters['extraQP'] parameters['deviceCodePath'] = '/oauth2/devicecode' parameters['deviceCodeUrlPath'] = parameters['authUrlResult'].path + parameters['deviceCodePath'] + parameters['extraQP'] parameters['authorizePath'] = '/oauth/authorize' parameters['authorizeUrlPath'] = parameters['authUrlResult'].path + parameters['authorizePath'] parameters['authorizeUrl'] = parameters['authUrlResult'].geturl() parameters['instanceDiscoverySuccessResponse'] = { 'tenant_discovery_endpoint' : parameters['authority'] } parameters['userRealmPathTemplate'] = '/common/UserRealm/' parameters['userRealmResponseFederated'] = '{\"account_type\":\"federated\",\"federation_protocol\":\"wstrust\",\"federation_metadata_url\":\"'+parameters['adfsMex']+'\",\"federation_active_auth_url\":\"'+parameters['adfsWsTrust']+'\",\"ver\":\"0.8\"}' parameters['userRealmResponseManaged'] = '{\"account_type\":\"managed\",\"federation_protocol\":\"wstrust\",\"federation_metadata_url\":\"'+parameters['adfsMex']+'\",\"federation_active_auth_url\":\"'+parameters['adfsWsTrust']+'\",\"ver\":\"0.8\"}' parameters['MexFile'] = os.path.join(_dirname, 'mex/common.mex.xml') parameters['RSTRFile'] = os.path.join(_dirname, 'wstrust/common.rstr.xml') parameters['AssertionFile'] = os.path.join(_dirname, 'wstrust/common.base64.encoded.assertion.txt') parameters['logContext'] = { 'correlation_id' : 'test-correlation-id-123456789' } parameters['callContext'] = { 'log_context' : parameters['logContext'] } # This is a dummy RSA private cert used for testing purpose.It does not represent valid credential. # privatePem variable is a fake certificate in the form of a string. def get_self_signed_cert(): private_pem = ("-----BEGIN PRIVATE KEY-----\n" "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDLTKKMn3WJiDnc" "8VuJPDr1kj9VYr9zdobMUSJ9YgRb6Rz05YiUOlSeNiMA6Y7yrpVbSxewIAkVC/gI" "W/Ywp4FtR9j/SzQ91HIHvmKBrOAorpPDS0ZQ6nfeaBtZ14UIF16H/OvgyMkweBBd" "7pIbF4i7ty3gdhFzpN2xd+qXTeDVMtxaQOM8RTAVp1RUpuNpPSIQxo9dLyOotcAe" "Fj8uc0mMa0o6DbdxD26RiBIOgzcsYr5WUCMihmE3h1EIbXtQC5YwFnU9Q8OgupWz" "i31QE1fbFgWzpjx1/KU9Yb8doWs1jwXSo4UGYecxiOKBrFuj0I6Kyelv9dfiayZF" "GpwR+FmXAgMBAAECggEABmSxk/SL0LhtAWrBsy4muIRR45CIbswibxh6GjFT68QH" "+heh1O+Eq7kOHsA5k54z6jwRUaOgRX4r3a9urZcG9fXVeCnYSb19nIq7NFLIdd8P" "nIuoeXD2NhNWENw7PcbmXSZyEI6f7RtJgHq5M4ro7OZU1gNAhz9/DU61HO8BDBNP" "9TT9h2Kf5cAHC79lrRs7Cj9yLK/JFFPFSyTEqxo9O6xHQfRv6X25rZeYo9JzGaqP" "mwTvmheEqW7a75apBEpbzqD0f0jT4anaOSab0D10LyD9qEiCpzgDKG8Q31c0y9zS" "Utk0suVR35abo22LdtvXMSyQMDfOOx3hqbUZ9c34uQKBgQD+oJUtfUYT9DBg/+jR" "t/u+Tq/VnpolciQiIpIvUArhIFmOzLkt/hjH8ozOJlRrFADUWAE+pSWuAMdPjAsi" "NGRYueAPQ29bqRF+5i0cJHXlxVNsqhF1SD5z/qaKU/MVL358v++g5shazQm45gUg" "BeXyeRc++aFBTUAjUyoDOCh+bQKBgQDMZTacfAcGORL8TVvjq+0IJ6IppICmwRVU" "FlZ/7HSJ+F1itggp0Kn9xzEk7SPgU8w0koysN+2189wn77PhQAAp1oGH2xvubXIz" "nnAFpS9XbmzrG3JhHEVJqMe2qZ/pFOSqZxnNAekyWcE3BLamBBrMIx4wJBpmxVkf" "EWUGSvolkwKBgDqQ8P8Pi2jXh7En64MhUFQLgUIfQtFOGaWIUhtzy6zQZgkEaat8" "gHKtBVn9Uvl2FmLBAzhHgA0vvKg9S+pIJrSJvFGGbzyj/JQ1mTaZ5Ew/QNsDmxRg" "04yWi/PRL142GF/VPebCbl8EPjI7Jf6hnKxS0df4TvDYNeJqJIWtCxNZAoGBAI04" "rUfnhe7txklepcujgW1t/OQqzdzpcXQczv0qAcdGPDe0r+U8UAeQ9kqeMniPTXtR" "ejKPngVmjUlmm/FZCAPgOrUEVcMiCZLSuHGeFRyipky3NQsVvmXLYNm7T0p67hcy" "jygPVvE8BHygHBaOpXlAFl6Kw1cYqaAGo7d6XGVTAoGBAL0FucFmEAZOH7Bcnl30" "JMXMcoedCAMMZG235cL2xBz6z+MzWVMkiZxblOVqAHRExGDoT01fymVte1OoKx7Q" "SKiGNXCVBatkk7PlRUVnL0ziSwgYVxNX9eGNXZRBXUH3BuoYPlfdUMH36vgmukbT" "Ui28YpkjQ5RY1UwUY6tk+Bka\n" "-----END PRIVATE KEY-----") public_pem = ("MIICoTCCAYkCAgPoMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNVBAMMCUNMSS1Mb2dp" "bjAiGA8yMDE4MTAxNzE2MTAxN1oYDzIwMTkxMDE3MTYxMDE5WjAUMRIwEAYDVQQD" "DAlDTEktTG9naW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLTKKM" "n3WJiDnc8VuJPDr1kj9VYr9zdobMUSJ9YgRb6Rz05YiUOlSeNiMA6Y7yrpVbSxew" "IAkVC/gIW/Ywp4FtR9j/SzQ91HIHvmKBrOAorpPDS0ZQ6nfeaBtZ14UIF16H/Ovg" "yMkweBBd7pIbF4i7ty3gdhFzpN2xd+qXTeDVMtxaQOM8RTAVp1RUpuNpPSIQxo9d" "LyOotcAeFj8uc0mMa0o6DbdxD26RiBIOgzcsYr5WUCMihmE3h1EIbXtQC5YwFnU9" "Q8OgupWzi31QE1fbFgWzpjx1/KU9Yb8doWs1jwXSo4UGYecxiOKBrFuj0I6Kyelv" "9dfiayZFGpwR+FmXAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBADfEqXzcI/fs82T0" "9B3H3lGWQL1JlcxOxD2TeMPtubDNllhZBT5GaYiw1LWAq+xJZZh+QPNxvZVw5Q/p" "wgXo32maLNwjuhlDl/5bNNOMsxszRz60C2QQXzIaBxd6T2EUcnMQozu5y/33HT8k" "k/ipBKbfmLP7Hgvs2xdhjHQcG61a2QP6qxD0UjVpXlgsL8wwc28ZSk1RqhxnHG0s" "HRrRuwNhqWRe7JCGNkOwUghlemrqSuL3i6iAaeqipBqS0vVFGN8KS12jKYirEV5T" "YkJ2HRrzSWEWbGhk+LnVis47nYRFzQB/sec/m+rpCpX6Spmiez6Yge2u874Oks/A" "OGQyeYk=") return private_pem, public_pem parameters['certHash'] = 'B8:D3:FC:F1:51:50:63:7F:B0:ED:EE:32:C5:A2:4B:A2:28:D8:93:91' parameters['nowDate'] = datetime.fromtimestamp(1418433646.179) parameters['jwtId'] = '09841beb-a2c2-4777-a347-34ef055238a8' parameters['expectedJwtWithThumbprint'] = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6InVOUDg4VkZRWTMtdzdlNHl4YUpMb2lqWWs1RT0ifQ.eyJleHAiOjE0MTg0MzQyNDYsImF1ZCI6bnVsbCwiaXNzIjoiZDY4MzU3MTMtYjc0NS00OGQxLWJiNjItN2E4MjQ4NDc3ZDM1IiwianRpIjoiMDk4NDFiZWItYTJjMi00Nzc3LWEzNDctMzRlZjA1NTIzOGE4IiwibmJmIjoxNDE4NDMzNjQ2LCJzdWIiOiJkNjgzNTcxMy1iNzQ1LTQ4ZDEtYmI2Mi03YTgyNDg0NzdkMzUifQ.sV5CPEQjYqlnGXhv2f8ozCpAD281is1aOjOHZRKQlPe8zuRhEC4DnAv66QcrxA9HkPs3OAR1GWHnlgVL88uCcAbdEgFo7cAVaQQeRr90zlDMOMoZqULXnorbO90q91BrnJdbcygzsba4Z_FPzKAsJ7J8NXWfWcbkFGrisjuyi97Nm-nCCpjH1zM6gi3paGg_53GFb2S7xMv1lvB7LfPQMI8QvOC64kmVia-cr2NQoT9XLz2U_1ahCKidN2ozyCv09shRjfBu2QSeIctbv0BKVfQQCUnLuMQ-O4_NKY3THZn5hl5PvFDPjlI3X_Om58gPhwISkgtndGTMJ9W-H5z71Q' parameters['expectedJwtWithPublicCert'] = 'eyJ4NWMiOiJNSUlDb1RDQ0FZa0NBZ1BvTUEwR0NTcUdTSWIzRFFFQkJRVUFNQlF4RWpBUUJnTlZCQU1NQ1VOTVNTMU1iMmRwYmpBaUdBOHlNREU0TVRBeE56RTJNVEF4TjFvWUR6SXdNVGt4TURFM01UWXhNREU1V2pBVU1SSXdFQVlEVlFRRERBbERURWt0VEc5bmFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFETFRLS01uM1dKaURuYzhWdUpQRHIxa2o5VllyOXpkb2JNVVNKOVlnUmI2UnowNVlpVU9sU2VOaU1BNlk3eXJwVmJTeGV3SUFrVkMvZ0lXL1l3cDRGdFI5ai9TelE5MUhJSHZtS0JyT0FvcnBQRFMwWlE2bmZlYUJ0WjE0VUlGMTZIL092Z3lNa3dlQkJkN3BJYkY0aTd0eTNnZGhGenBOMnhkK3FYVGVEVk10eGFRT004UlRBVnAxUlVwdU5wUFNJUXhvOWRMeU9vdGNBZUZqOHVjMG1NYTBvNkRiZHhEMjZSaUJJT2d6Y3NZcjVXVUNNaWhtRTNoMUVJYlh0UUM1WXdGblU5UThPZ3VwV3ppMzFRRTFmYkZnV3pwangxL0tVOVliOGRvV3MxandYU280VUdZZWN4aU9LQnJGdWowSTZLeWVsdjlkZmlheVpGR3B3UitGbVhBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUZCUUFEZ2dFQkFEZkVxWHpjSS9mczgyVDA5QjNIM2xHV1FMMUpsY3hPeEQyVGVNUHR1YkRObGxoWkJUNUdhWWl3MUxXQXEreEpaWmgrUVBOeHZaVnc1US9wd2dYbzMybWFMTndqdWhsRGwvNWJOTk9Nc3hzelJ6NjBDMlFRWHpJYUJ4ZDZUMkVVY25NUW96dTV5LzMzSFQ4a2svaXBCS2JmbUxQN0hndnMyeGRoakhRY0c2MWEyUVA2cXhEMFVqVnBYbGdzTDh3d2MyOFpTazFScWh4bkhHMHNIUnJSdXdOaHFXUmU3SkNHTmtPd1VnaGxlbXJxU3VMM2k2aUFhZXFpcEJxUzB2VkZHTjhLUzEyaktZaXJFVjVUWWtKMkhScnpTV0VXYkdoaytMblZpczQ3bllSRnpRQi9zZWMvbStycENwWDZTcG1pZXo2WWdlMnU4NzRPa3MvQU9HUXllWWs9IiwieDV0IjoidU5QODhWRlFZMy13N2U0eXhhSkxvaWpZazVFPSIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJqdGkiOiIwOTg0MWJlYi1hMmMyLTQ3NzctYTM0Ny0zNGVmMDU1MjM4YTgiLCJleHAiOjE0MTg0MzQyNDYsImF1ZCI6bnVsbCwic3ViIjoiZDY4MzU3MTMtYjc0NS00OGQxLWJiNjItN2E4MjQ4NDc3ZDM1IiwibmJmIjoxNDE4NDMzNjQ2LCJpc3MiOiJkNjgzNTcxMy1iNzQ1LTQ4ZDEtYmI2Mi03YTgyNDg0NzdkMzUifQ.ROcEKjjuKN0-iK4seRCYftvEh8F5Esj1Y3NJF0MbUGWZQYTRnjibJAnVkvmCqFSGT_mDFhasTM67pwAWtfYNP875UM87HG4aUyZG48pFojnWxnMMf9gBardPmpaDNi3U_iIGoTGVLR60JV30WjsOCkEJY79l68EMc5i6XqYtOSyJDlI0rn8ZTqoyVHQYqCwkTLDF0cqTrqK6HV9iWuiT0rq3LMP2lShwAhKaTYIeAAek5Bw5LwRR2mo9ybreq_02vCDxIQg0C3kBDGMU8GxQ2tAWMYSqnxNfrjgUhARDQYdZTjCyuq1kOb8QrHly29mPT7xdS7Xnc0IF6JZb1PXj0Q' parameters['cert'], parameters['publicCert'] = get_self_signed_cert() correlation_id_regex = re.compile("[^\s]+") def set_correlation_id(correlation_id=None): global correlation_id_regex correlation_id_regex = correlation_id if correlation_id else correlation_id_regex def turn_on_logging(level='DEBUG', handler = None): log.set_logging_options({ 'level' : level, 'handler' : handler }) def reset_logging(): pass def clear_static_cache(): pass TOKEN_RESPONSE_MAP = { 'token_type' : 'tokenType', 'access_token' : 'accessToken', 'refresh_token' : 'refreshToken', 'created_on' : 'createdOn', 'expires_on' : 'expiresOn', 'expires_in' : 'expiresIn', 'error' : 'error', 'error_description' : 'errorDescription', 'resource' : 'resource', } DEVICE_CODE_RESPONSE_MAP = { 'device_code' : 'deviceCode', 'user_code' : 'userCode', 'verification_url' : 'verificationUrl', 'interval' : 'interval', 'expires_in' : 'expiresIn', 'error' : 'error', 'error_description' : 'errorDescription' } def dicts_equal(expected, actual): ''' Compares two dictionaries and returns an error message if something is wrong. None otherwise ''' if not len(expected) == len(actual): return 'dicts are not the same length' shared_items = set(expected.keys()) & set(actual.keys()) if not len(shared_items) == len(expected): return 'The provided dicts do not have the same keys' for i in expected.keys(): expected_value = expected[i] actual_value = actual[i] if not expected_value == actual_value: return 'Not Equal: expected:{} actual:{}'.format(expected_value[i], actual_value[i]) return None def map_fields(in_obj, out_obj, map): for key in in_obj.keys(): if map.get(key): mapped = map[key] out_obj[mapped] = in_obj[key] def create_response(options = None, iteration = None): options = options if options else {} authority = options.get('authority', parameters['authorityTenant']) base_response = { 'token_type' : 'Bearer', 'expires_in': 28800 } resource = options.get('resource', parameters['resource']) iterated = { 'access_token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1THdqcHdBSk9NOW4tQSJ9.eyJhdWQiOiIwMDAwMDAwMi0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC82MmYwMzQ3MS02N2MxLTRjNTAtYjlkMS0xMzQ1MDc5ZDk3NzQvIiwiaWF0IjoxMzc4NjAxMTY4LCJuYmYiOjEzNzg2MDExNjgsImV4cCI6MTM3ODYyOTk2OCwidmVyIjoiMS4wIiwidGlkIjoiNjJmMDM0NzEtNjdjMS00YzUwLWI5ZDEtMTM0NTA3OWQ5Nzc0Iiwib2lkIjoiZjEzMDkzNDEtZDcyMy00YTc1LTk2YzktNGIyMTMzMzk0Mjg3Iiwic3ViIjoiZjEzMDkzNDEtZDcyMy00YTc1LTk2YzktNGIyMTMzMzk0Mjg3IiwiYXBwaWQiOiI1YzI1ZDFiZi1iMjMyLTQwMzUtYjZiOS0yYjdlN2U4MzQ2ZDYiLCJhcHBpZGFjciI6IjEifQ.qXM7f9TTiLApxVMwaSrISQQ6UAnfKvKhoIlN9rB0Eff2VXvIWKGRsclPkMQ5x42BQz2N6pSXEsN-LsNCZlQ76Rc3rVRONzeCYh7q_NXcCJG_d6SJTtV5GBfgqFlgT8UF5rblabbMdOiOrddvJm048hWt2Nm3qD3QjQdPBlD7Ksn-lUR1jEJPIqDaBjGom8RawrZTW6X1cy1Kr8mEYFkxcbU91k_RZUumONep9FTR8gfPkboeD8zyvOy64UeysEtcuaNCfhHSBFcwC8MwjUr5r_T7au7ywAcYDOVgoa7oF_dN1JNweiDoNNZ9tyUS-RY3sa3-gXk77gRxpA4CkpittQ', 'resource' : resource } if not options.get('noRefresh'): if options.get('refreshedRefresh'): iterated['refresh_token'] = 'AwABAAAAvPM1KaPlrEqdFSBzjqfTGCDeE7YHWD9jkU2WWYKLjxu928QAbkoFyWp&yfPNft8DcbBqOSYVq5Ty_60YICIdFw61SG4-eT1nWHNOPdzsL2ZzloUsp2DpqlIr1s5Z3953oQBi7dOqiHk37NXQqmNEJ7MfmDp6w3EOa29EPARvjGIHFgtICW1-Y82npw1v1g8Ittb02pksNU2XzH2X0E3l3TuSZWsX5lpl-kfPOc8zppU6bwvT-VOPHZVVLQedDIQZyOiFst9HLUjbiIvBgV7tNwbB4H5yF56QQscz49Nrb3g0ibuNDo7efFawLzNoVHzoTrOTcCGSG1pt8Z-npByrEe7vg1o4nNFjspuxlyMGdnYRAnaZfvgzqROP_m7ZqSd6IAA' else: iterated['refresh_token'] = parameters['refreshToken'] else: iterated['refresh_token'] = None if iteration: for key in iterated.keys(): iterated[key] = iterated[key] + iteration base_response.update(iterated) if not options.get('mrrt', False): del base_response['resource'] date_now = datetime.now() wire_response = dict(base_response) wire_response['created_on'] = time.mktime(date_now.timetuple()) decoded_response = {} map_fields(wire_response, decoded_response, TOKEN_RESPONSE_MAP) decoded_response['createdOn'] = str(date_now) if not options.get('noIdToken', None): if options.get('urlSafeUserId', None): wire_response['id_token'] = encoded_id_token_url_safe parsed_user_info = parsed_id_token_url_safe else: wire_response['id_token'] = encoded_id_token parsed_user_info = parsed_id_token decoded_response.update(parsed_user_info) if options.get('expired'): expires_on_date = datetime.now() - timedelta(1) else: expires_on_date = datetime.now() + timedelta(seconds=decoded_response.get('expiresIn',0)) decoded_response['expiresOn'] = str(expires_on_date) cached_response = dict(decoded_response) cached_response['_clientId'] = parameters['clientId'] cached_response['_authority'] = authority cached_response['resource'] = iterated['resource'] if options.get('mrrt', False): cached_response['isMRRT'] = True return { 'wireResponse' : wire_response, 'decodedResponse' : decoded_response, 'cachedResponse' : cached_response, 'decodedIdToken' : decoded_id_token, 'resource' : iterated['resource'], 'refreshToken' : iterated['refresh_token'], 'clientId' : cached_response['_clientId'], 'authority' : authority, } def compare_query_strings(left, right): left_params = urlencode(left) right_params = urlencode(right) return left_params == right_params def filter_query_strings(expected, received): return expected if compare_query_strings(expected, received) else received def remove_query_string_if_matching(path, query): path_url = urlparse(path) return path_url.path if compare_query_strings(path_url.query, query) else path def val_exists(val): return val if val else False def match_standard_request_headers(mock_request): matches = [] matches.append(mock_request.headers.get('x-client-SKU', None) == 'Python') assert mock_request.headers.get('x-client-Ver') is not None matches.append(mock_request.headers.get('x-client-OS', None) != None) matches.append(mock_request.headers.get('x-client-CPU', None) != None) request_id = correlation_id_regex.match(mock_request.headers.get('client-request-id')) matches.append(request_id != None) if not all(matches): raise AssertionError("Not all the standard request headers matched.") def setup_expected_oauth_response(queryParameters, tokenPath, httpCode, returnDoc, authorityEndpoint): query = urlencode(queryParameters) url = "{}/{}?{}".format(authorityEndpoint.rstrip('/'), tokenPath.lstrip('/'), query) httpretty.register_uri(httpretty.POST, url, json.dumps(returnDoc), status = httpCode, content_type = 'text/json') def setup_expected_client_cred_token_request_response(http_code, return_doc=None, authority_endpoint = None): auth_endpoint = authority_endpoint or parameters['authUrl'] query = { 'grant_type' : 'client_credentials', 'client_id' : parameters['clientId'], 'client_secret' : parameters['clientSecret'], 'resource' : parameters['resource'] } setup_expected_oauth_response(query, parameters['tokenPath'] + parameters['extraQP'], http_code, return_doc, auth_endpoint) def setup_expected_instance_discovery_request(http_code, discovery_host, return_doc, authority): protocol = 'https://' host = discovery_host pathname = '/common/discovery/instance' query = {} query['authorization_endpoint'] = authority query['api-version'] = '1.0' query_string = urlencode(query) url = "{}{}{}?{}".format(protocol, host, pathname, query_string) httpretty.register_uri(httpretty.GET, url, json.dumps(return_doc), status = http_code, content_type = 'text/json') def setup_expected_user_realm_response(http_code, return_doc, authority=None): user_realm_authority = authority or parameters['authority'] user_realm_authority = urlparse(user_realm_authority) # Get Base URL user_realm_authority = '{}://{}'.format(user_realm_authority.scheme, user_realm_authority.netloc) user_realm_path = parameters['userRealmPathTemplate'].replace('', parameters['username']) query = 'api-version=1.0' url = '{}/{}?{}'.format(user_realm_authority.rstrip('/'), user_realm_path.lstrip('/'), query) httpretty.register_uri(httpretty.GET, url, return_doc) def setup_expected_user_realm_response_common(federated): if federated: response_doc = parameters['userRealmResponseFederated'] else: response_doc = parameters['userRealmResponseManaged'] return setup_expected_user_realm_response(200, response_doc, parameters['authority']) def setup_expected_refresh_token_request_response(http_code, return_doc, authority_endpoint=None, resource=None, client_secret=None): auth_endpoint = authority_endpoint or parameters['authority'] query_parameters = {} query_parameters['grant_type'] = 'refresh_token' query_parameters['client_id'] = parameters['clientId'] if client_secret: query_parameters['client_secret'] = client_secret if resource: query_parameters['resource'] = resource query_parameters['refresh_token'] = parameters['refreshToken'] return setup_expected_oauth_response(query_parameters, parameters['tokenUrlPath'], http_code, return_doc, auth_endpoint) def setup_expected_mex_wstrust_request_common(): with open(parameters['MexFile']) as mex: mex_doc = mex.read() httpretty.register_uri(httpretty.GET, parameters['adfsUrlNoPath'] + parameters['adfsMexPath'], mex_doc) with open(parameters['RSTRFile']) as resr: rest_doc = resr.read() httpretty.register_uri(httpretty.POST, parameters['adfsUrlNoPath'] + parameters['adfsWsTrustPath'], rest_doc) def create_empty_adal_object(): context = log.create_log_context() component = 'TEST' logger = log.Logger(component, context) call_context = {'log_context' : context } adal_object = { 'log' : logger, 'call_context' : call_context } return adal_object def is_date_within_tolerance(date, expected_date = None): expected = expected_date or datetime.today() min_range = expected - timedelta(0, 5000) max_range = expected + timedelta(0, 5000) if date >= min_range and date <= max_range: return True return False def is_expires_within_tolerance(expires_on): # Add the expected expires_in latency. expectedExpires = datetime.now() + timedelta(0, 28800) return is_date_within_tolerance(expires_on, expectedExpires) def is_match_token_response(expected, received): if not received: raise Exception("Token Response received is None") expiresOn = received.get('expiresOn', None) createdOn = received.get('createdOn', None) if expiresOn: expiresOnTime = dateutil.parser.parse(expiresOn) if not is_expires_within_tolerance(expiresOnTime): return False if createdOn: createdOnTime = dateutil.parser.parse(createdOn) if not is_date_within_tolerance(createdOnTime): return False # Compare the expected and responses without the expires_on field as that was validated above. import copy received_copy = copy.deepcopy(received) received_copy.pop('expiresOn', None) received_copy.pop('createdOn', None) expected_copy = copy.deepcopy(expected) expected_copy.pop('expiresOn', None) expected_copy.pop('createdOn', None) if received_copy.get('clientId', None) and not expected_copy.get('clientId', None): received_copy.pop('clientId', None) expect_empty = dicts_equal(expected_copy, received_copy) return expect_empty is None azure-activedirectory-library-for-python-1.2.7/tests/wstrust/000077500000000000000000000000001403263465100245135ustar00rootroot00000000000000azure-activedirectory-library-for-python-1.2.7/tests/wstrust/RST.xml000066400000000000000000000033561403263465100257140ustar00rootroot00000000000000 http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue %MESSAGE_ID% http://www.w3.org/2005/08/addressing/anonymous %WSTRUST_ENDPOINT% %CREATED% %EXPIRES% %USERNAME% %PASSWORD% %APPLIES_TO% http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue azure-activedirectory-library-for-python-1.2.7/tests/wstrust/RSTR.xml000066400000000000000000000212151403263465100260300ustar00rootroot00000000000000 http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal 2013-11-15T03:08:25.221Z 2013-11-15T03:13:25.221Z 2013-11-15T03:08:25.205Z 2013-11-15T04:08:25.205Z https://login.microsoftonline.com/extSTS.srf https://login.microsoftonline.com/extSTS.srf 1TIu064jGEmmf+hnI+F0Jg== urn:oasis:names:tc:SAML:1.0:cm:bearer frizzo@richard-randall.com 1TIu064jGEmmf+hnI+F0Jg== 1TIu064jGEmmf+hnI+F0Jg== urn:oasis:names:tc:SAML:1.0:cm:bearer 3i95D+nRbsyRitSPeT7ZtEr5vbM= aVNmmKLNdAlBxxcNciWVfxynZUPR9ql8ZZSZt/qpqL/GB3HX/cL/QnfG2OOKrmhgEaR0Ul4grZhGJxlxMPDL0fhnBz+VJ5HwztMFgMYs3Md8A2sZd9n4dfu7+CByAna06lCwwfdFWlNV1MBFvlWvYtCLNkpYVr/aglmb9zpMkNxEOmHe/cwxUtYlzH4RpIsIT5pruoJtUxKcqTRDEeeYdzjBAiJuguQTChLmHNoMPdX1RmtJlPsrZ1s9R/IJky7fHLjB7jiTDceRCS5QUbgUqYbLG1MjFXthY2Hr7K9kpYjxxIk6xmM7mFQE3Hts3bj6UU7ElUvHpX9bxxk3pqzlhg== MIIC6DCCAdCgAwIBAgIQaztYF2TpvZZG6yreA3NRpzANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVBREZTIFNpZ25pbmcgLSBmcy5yaWNoYXJkLXJhbmRhbGwuY29tMB4XDTEzMTExMTAzNTMwMFoXDTE0MTExMTAzNTMwMFowMDEuMCwGA1UEAxMlQURGUyBTaWduaW5nIC0gZnMucmljaGFyZC1yYW5kYWxsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO+1VWY/sYDdN3hdsvT+mWHTcOwjp2G9e0AEZdmgh7bS54WUJw9y0cMxJmGB0jAAW40zomzIbS8/o3iuxcJyFgBVtMFfXwFjVQJnZJ7IMXFs1V/pJHrwWHxePz/WzXFtMaqEIe8QummJ07UBg9UsYZUYTGO9NDGw1Yr/oRNsl7bLA0S/QlW6yryf6l3snHzIgtO2xiWn6q3vCJTTVNMROkI2YKNKdYiD5fFD77kFACfJmOwP8MN9u+HM2IN6g0Nv5s7rMyw077Co/xKefamWQCB0jLpv89jo3hLgkwIgWX4cMVgHSNmdzXSgC3owG8ivRuJDATh83GiqI6jzA1+x4rkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAxA5MQZHw9lJYDpU4f45EYrWPEaAPnncaoxIeLE9fG14gA01frajRfdyoO0AKqb+ZG6sePKngsuq4QHA2EnEI4Di5uWKsXy1Id0AXUSUhLpe63alZ8OwiNKDKn71nwpXnlGwKqljnG3xBMniGtGKrFS4WM+joEHzaKpvgtGRGoDdtXF4UXZJcn2maw6d/kiHrQ3kWoQcQcJ9hVIo8bC0BPvxV0Qh4TF3Nb3tKhaXsY68eMxMGbHok9trVHQ3Vew35FuTg1JzsfCFSDF8sxu7FJ4iZ7VLM8MQLnvIMcubLJvc57EHSsNyeiqBFQIYkdg7MSf+Ot2qJjfExgo+NOtWN+g== _9bd2b280-f153-471a-9b73-c1df0d555075 _9bd2b280-f153-471a-9b73-c1df0d555075 urn:oasis:names:tc:SAML:1.0:assertion http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer azure-activedirectory-library-for-python-1.2.7/tests/wstrust/common.base64.encoded.assertion.txt000066400000000000000000000120201403263465100332300ustar00rootroot00000000000000PHNhbWw6QXNzZXJ0aW9uIE1ham9yVmVyc2lvbj0iMSIgTWlub3JWZXJzaW9uPSIxIiBBc3NlcnRpb25JRD0iX2JmMTM3ZjkwLTdkZDctNDY2OC04YTM5LThiZjU1ZWI1MjAxNyIgSXNzdWVyPSJodHRwOi8vZnMubmF0dXJhbGNhdXNlcy5jb20vYWRmcy9zZXJ2aWNlcy90cnVzdCIgSXNzdWVJbnN0YW50PSIyMDE0LTAxLTI3VDA4OjE1OjQ1LjAwM1oiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMDphc3NlcnRpb24iPjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE0LTAxLTI3VDA4OjE1OjQ1LjAwM1oiIE5vdE9uT3JBZnRlcj0iMjAxNC0wMS0yN1QwOToxNTo0NS4wMDNaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uQ29uZGl0aW9uPjxzYW1sOkF1ZGllbmNlPnVybjpmZWRlcmF0aW9uOk1pY3Jvc29mdE9ubGluZTwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbkNvbmRpdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSWRlbnRpZmllciBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkIj40M1lSelRyaCtVZThlVGVjbnFMQXhRPT08L3NhbWw6TmFtZUlkZW50aWZpZXI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48c2FtbDpDb25maXJtYXRpb25NZXRob2Q+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4wOmNtOmJlYXJlcjwvc2FtbDpDb25maXJtYXRpb25NZXRob2Q+PC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+PC9zYW1sOlN1YmplY3Q+PHNhbWw6QXR0cmlidXRlIEF0dHJpYnV0ZU5hbWU9IlVQTiIgQXR0cmlidXRlTmFtZXNwYWNlPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy9jbGFpbXMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPmZyaXp6b0BuYXR1cmFsY2F1c2VzLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBBdHRyaWJ1dGVOYW1lPSJJbW11dGFibGVJRCIgQXR0cmlidXRlTmFtZXNwYWNlPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL0xpdmVJRC9GZWRlcmF0aW9uLzIwMDgvMDUiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPjQzWVJ6VHJoK1VlOGVUZWNucUxBeFE9PTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXV0aGVudGljYXRpb25TdGF0ZW1lbnQgQXV0aGVudGljYXRpb25NZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMDphbTpwYXNzd29yZCIgQXV0aGVudGljYXRpb25JbnN0YW50PSIyMDE0LTAxLTI3VDA4OjE1OjQ0Ljk4N1oiPjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlkZW50aWZpZXIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDp1bnNwZWNpZmllZCI+NDNZUnpUcmgrVWU4ZVRlY25xTEF4UT09PC9zYW1sOk5hbWVJZGVudGlmaWVyPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24+PHNhbWw6Q29uZmlybWF0aW9uTWV0aG9kPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMDpjbTpiZWFyZXI8L3NhbWw6Q29uZmlybWF0aW9uTWV0aG9kPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0Pjwvc2FtbDpBdXRoZW50aWNhdGlvblN0YXRlbWVudD48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz48ZHM6UmVmZXJlbmNlIFVSST0iI19iZjEzN2Y5MC03ZGQ3LTQ2NjgtOGEzOS04YmY1NWViNTIwMTciPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPkI1ZUVJa1RoZ1M1NDQ1WkJaYVdXYmlxM1Vtdz08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+Yk0vQUVMOElzcWs3a0d4RFB2Y2lDN1JsbXI5ZmZSV0doa25wUmM0TFVDMEJqUWlZb28xMU5ZN3hoMFM1QSt4S0lvWE1nZWFhUDZxamIwbjI3VE5UM2craE9jQityOXg2SDVoQU5zNHJ0bC91T0VNMHBLdmNnQlkwYmh6NEhEUHFhaVJwQVZJdGdpU0dudERJZWc0MmNPaFNZSjlPbjZvR1FjVkE1aHkyR210eHhrN2Q3YzRTcTJueW0zdDNEM0RMTU9md3ZsdmlCWnNkQmdsVWc0aVpyUHU4cWdUUnJVbmZHaTVNMVZzVHVTVHdLVng3TkJOTGZqQnhBaXBtck0wUHlPSWs0M0NIQzNMSmpUQ3phajd2UlNxQmJYRjNMSW0yRGMwSFVMYlViZ3dPS3JvNzhyN2JkQ0Yzc0xmYlUyd2dUWnl2SWtYN0I3K29vdnN3RjZHb3l3PT08L2RzOlNpZ25hdHVyZVZhbHVlPjxLZXlJbmZvIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48WDUwOURhdGE+PFg1MDlDZXJ0aWZpY2F0ZT5NSUlDNURDQ0FjeWdBd0lCQWdJUUdWTHpkbnZrQWFaSFp1ZlVpN3p2cVRBTkJna3Foa2lHOXcwQkFRc0ZBREF1TVN3d0tnWURWUVFERXlOQlJFWlRJRk5wWjI1cGJtY2dMU0JtY3k1dVlYUjFjbUZzWTJGMWMyVnpMbU52YlRBZUZ3MHhNekV4TVRBd01qRXhORGxhRncweE5ERXhNVEF3TWpFeE5EbGFNQzR4TERBcUJnTlZCQU1USTBGRVJsTWdVMmxuYm1sdVp5QXRJR1p6TG01aGRIVnlZV3hqWVhWelpYTXVZMjl0TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2c0psY2ttWjRqWkVKREJISG1kSDBDbGdPNENwNWJ2Z0FlTmVKTFFrSGxybkhEME81L2JreU1vNGFOZy9Gb0pKY0V0MFh6emQ4MTZsbkRZek0yVVRhV0Foa0hOYmw0c1ZXNGR0TFhlS0hlR3l5NDNVYlJZWWhJcERJSzhOaWN5UmRoeE5ualhiWkVNY3VROG5YcmJrajNETW5sQkVNLzVocFM3MzMreVZZclVrN0JjbXhhYjFsRFJVT0xiTDVLaDM1RzJKa1g4aUN4elVySFZqMTVEbmVHVlFHeUZPbWYyRHBDOUNOZXAxMjNYWWRYT2Z0WHQ0TmgxKy9lZDExemplWlhlUThobjM2LzNOSituNG1Ja0JlREdYbWhCZGg4TUFaV0NnVXgzRytTZGlmQzNiVlUzQnJWV2VQb2NUaUg2aGQxbGpMMmNWaDdoaEVJT3RHSEQ0S3dJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUNESXRobml2MUhrL05qSGtsQlhvVElYUFhXZVNhNThaWmZ0b3IwbzBxbHFaWDFoQWx1WHhKZUxLV3R1aUpLa1pvL2VUam9QaWRPM0wvWDBwc1pSK2NtTXNiRUE5dHRBTzhBa3FldVl4amx3MFlrZnFyWDZ5Uk5sa0dXcjRTVTA3WmdmSmhvVHNDUDlvT0JHL3gyeERrQUdmQUVkeUJ5RXZHa2F0MTZIZjJFTEEzZm9DcVVXOE1HSk0xNklEbXU2SzlLckdBT1IzZGEwUHdRNC9zRVRIa2gxQWc1amtZNjlsSjdyM01nemRNTVpEYzh0UFFhelZaYmUweGMxdThXRzUyYzJVR29heTl6TnFNUUpPR25VWk5COWZWSGF6QTJwdk1oQmJ5QlNxbzFqUzMxQlhMbG9kN3Y1TkVNOEY1QUdpa1V3WUhWK0VaL08ydDQ5MWdjRmpjOTwvWDUwOUNlcnRpZmljYXRlPjwvWDUwOURhdGE+PC9LZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjwvc2FtbDpBc3NlcnRpb24+azure-activedirectory-library-for-python-1.2.7/tests/wstrust/common.rstr.xml000066400000000000000000000150241403263465100275200ustar00rootroot00000000000000http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal2014-01-27T08:15:45.003Z2014-01-27T08:20:45.003Z2014-01-27T08:15:45.003Z2014-01-27T09:15:45.003Zurn:federation:MicrosoftOnlineurn:federation:MicrosoftOnline43YRzTrh+Ue8eTecnqLAxQ==urn:oasis:names:tc:SAML:1.0:cm:bearerfrizzo@naturalcauses.com43YRzTrh+Ue8eTecnqLAxQ==43YRzTrh+Ue8eTecnqLAxQ==urn:oasis:names:tc:SAML:1.0:cm:bearerB5eEIkThgS5445ZBZaWWbiq3Umw=bM/AEL8Isqk7kGxDPvciC7Rlmr9ffRWGhknpRc4LUC0BjQiYoo11NY7xh0S5A+xKIoXMgeaaP6qjb0n27TNT3g+hOcB+r9x6H5hANs4rtl/uOEM0pKvcgBY0bhz4HDPqaiRpAVItgiSGntDIeg42cOhSYJ9On6oGQcVA5hy2Gmtxxk7d7c4Sq2nym3t3D3DLMOfwvlviBZsdBglUg4iZrPu8qgTRrUnfGi5M1VsTuSTwKVx7NBNLfjBxAipmrM0PyOIk43CHC3LJjTCzaj7vRSqBbXF3LIm2Dc0HULbUbgwOKro78r7bdCF3sLfbU2wgTZyvIkX7B7+oovswF6Goyw==MIIC5DCCAcygAwIBAgIQGVLzdnvkAaZHZufUi7zvqTANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNBREZTIFNpZ25pbmcgLSBmcy5uYXR1cmFsY2F1c2VzLmNvbTAeFw0xMzExMTAwMjExNDlaFw0xNDExMTAwMjExNDlaMC4xLDAqBgNVBAMTI0FERlMgU2lnbmluZyAtIGZzLm5hdHVyYWxjYXVzZXMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvsJlckmZ4jZEJDBHHmdH0ClgO4Cp5bvgAeNeJLQkHlrnHD0O5/bkyMo4aNg/FoJJcEt0Xzzd816lnDYzM2UTaWAhkHNbl4sVW4dtLXeKHeGyy43UbRYYhIpDIK8NicyRdhxNnjXbZEMcuQ8nXrbkj3DMnlBEM/5hpS733+yVYrUk7Bcmxab1lDRUOLbL5Kh35G2JkX8iCxzUrHVj15DneGVQGyFOmf2DpC9CNep123XYdXOftXt4Nh1+/ed11zjeZXeQ8hn36/3NJ+n4mIkBeDGXmhBdh8MAZWCgUx3G+SdifC3bVU3BrVWePocTiH6hd1ljL2cVh7hhEIOtGHD4KwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCDIthniv1Hk/NjHklBXoTIXPXWeSa58ZZftor0o0qlqZX1hAluXxJeLKWtuiJKkZo/eTjoPidO3L/X0psZR+cmMsbEA9ttAO8AkqeuYxjlw0YkfqrX6yRNlkGWr4SU07ZgfJhoTsCP9oOBG/x2xDkAGfAEdyByEvGkat16Hf2ELA3foCqUW8MGJM16IDmu6K9KrGAOR3da0PwQ4/sETHkh1Ag5jkY69lJ7r3MgzdMMZDc8tPQazVZbe0xc1u8WG52c2UGoay9zNqMQJOGnUZNB9fVHazA2pvMhBbyBSqo1jS31BXLlod7v5NEM8F5AGikUwYHV+EZ/O2t491gcFjc9_bf137f90-7dd7-4668-8a39-8bf55eb52017_bf137f90-7dd7-4668-8a39-8bf55eb52017urn:oasis:names:tc:SAML:1.0:assertionhttp://docs.oasis-open.org/ws-sx/ws-trust/200512/Issuehttp://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer