diff --git a/README.md b/README.md index 9ae32847..fd810480 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ --------------------|-----------------|--------------- [![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)| [Samples](https://github.com/azure-samples?query=active-directory)| [Support](README.md#community-help-and-support) +|[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) | --- | --- | --- | --- | @@ -17,7 +17,9 @@ You can learn in detail about ADAL Python functionality and usage documented in 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 [sample applications on GitHub](https://github.com/azure-samples?utf8=%E2%9C%93&q=active-directory&type=&language=) to help you get started with learning the Azure Identity system. This includes tutorials for native clients and web applications. We also provide full walkthroughs for authentication flows such as OAuth2, OpenID Connect and for calling APIs such as the Graph API. +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). @@ -25,19 +27,19 @@ The documents on [Auth Scenarios](https://docs.microsoft.com/en-us/azure/active- ## Versions -This library follows [Semantic Versioning](http://semver.org/). +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](http://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 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: [http://stackoverflow.com/questions/tagged/adal](http://stackoverflow.com/questions/tagged/adal) +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) ## 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](http://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. +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 diff --git a/adal/__init__.py b/adal/__init__.py index f9c21fd7..15617d01 100644 --- a/adal/__init__.py +++ b/adal/__init__.py @@ -27,7 +27,7 @@ # pylint: disable=wrong-import-position -__version__ = '1.2.1' +__version__ = '1.2.2' import logging diff --git a/adal/authentication_context.py b/adal/authentication_context.py index 8b5ca270..18297a13 100644 --- a/adal/authentication_context.py +++ b/adal/authentication_context.py @@ -181,7 +181,7 @@ def token_func(self): 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 auhtorization code for a + '''Gets a token for a given resource via authorization code for a server app. :param str authorization_code: An authorization code returned from a diff --git a/adal/authority.py b/adal/authority.py index a5621af2..26df134a 100644 --- a/adal/authority.py +++ b/adal/authority.py @@ -78,7 +78,12 @@ def _validate_authority_url(self): 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 authority url must be of the format https://login.microsoftonline.com/your_tenant") + 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('/')) diff --git a/adal/self_signed_jwt.py b/adal/self_signed_jwt.py index 66bfb9a8..67c4e887 100644 --- a/adal/self_signed_jwt.py +++ b/adal/self_signed_jwt.py @@ -64,6 +64,21 @@ def _raise_on_invalid_jwt_signature(encoded_jwt): 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 @@ -82,7 +97,7 @@ def _create_header(self, thumbprint, public_certificate): x5t = _create_x5t_value(thumbprint) header = {'typ':'JWT', 'alg':'RS256', 'x5t':x5t} if public_certificate: - header['x5c'] = 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}) diff --git a/adal/token_cache.py b/adal/token_cache.py index ab59d932..baae7915 100644 --- a/adal/token_cache.py +++ b/adal/token_cache.py @@ -52,6 +52,9 @@ def __eq__(self, other): _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): diff --git a/adal/token_request.py b/adal/token_request.py index 14629489..66743c4c 100644 --- a/adal/token_request.py +++ b/adal/token_request.py @@ -26,7 +26,6 @@ #------------------------------------------------------------------------------ from base64 import b64encode -import re from . import constants from . import log @@ -257,18 +256,14 @@ def _get_token_username_password_federated(self, username, password): username, password) @staticmethod def _parse_wstrust_version_from_federation_active_authurl(federation_active_authurl): - wstrust2005_regex = r'[/trust]?[2005][/usernamemixed]?' - wstrust13_regex = r'[/trust]?[13][/usernamemixed]?' - - if re.search(wstrust2005_regex, federation_active_authurl): + if '/trust/2005/usernamemixed' in federation_active_authurl: return WSTrustVersion.WSTRUST2005 - elif re.search(wstrust13_regex, federation_active_authurl): + 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.info("Acquiring token with username password.") + self._log.debug("Acquiring token with username password.") self._user_id = username try: token = self._find_token_from_cache() @@ -301,7 +296,7 @@ def get_token_with_username_password(self, username, password): return token def get_token_with_client_credentials(self, client_secret): - self._log.info("Getting token with client credentials.") + self._log.debug("Getting token with client credentials.") try: token = self._find_token_from_cache() if token: @@ -347,7 +342,7 @@ 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.info("Getting token from cache with refresh if necessary.") + self._log.debug("Getting token from cache with refresh if necessary.") self._user_id = user_id return self._find_token_from_cache() diff --git a/adal/util.py b/adal/util.py index d7ee1f70..1c84b479 100644 --- a/adal/util.py +++ b/adal/util.py @@ -74,7 +74,7 @@ def create_request_options(self, *options): def log_return_correlation_id(log, operation_message, response): if response and response.headers and response.headers.get('client-request-id'): - log.info("{} Server returned this correlation_id: {}".format( + log.debug("{} Server returned this correlation_id: {}".format( operation_message, response.headers['client-request-id'])) diff --git a/requirements.txt b/requirements.txt index 3457060d..cf913d2a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ requests==2.20.0 -PyJWT==1.0.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 -cryptography==2.3.0 +cryptography==2.3.1 #for testing httpretty==0.8.14 pylint==1.5.4 diff --git a/tests/test_authority.py b/tests/test_authority.py index cd6ab331..f72ceaaf 100644 --- a/tests/test_authority.py +++ b/tests/test_authority.py @@ -188,8 +188,7 @@ def test_bad_url_has_query(self): @httpretty.activate def test_url_extra_path_elements(self): - with six.assertRaisesRegex(self, ValueError, "The authority url must be of the format "+ - "https://login.microsoftonline.com/your_tenant"): + with six.assertRaisesRegex(self, ValueError, "tenant"): # Some tenant specific error message context = AuthenticationContext(self.nonHardCodedAuthority + '/extra/path') @httpretty.activate