Hello community, here is the log from the commit of package python-msal for openSUSE:Factory checked in at 2020-10-02 17:27:23 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-msal (Old) and /work/SRC/openSUSE:Factory/.python-msal.new.4249 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-msal" Fri Oct 2 17:27:23 2020 rev:3 rq:833121 version:1.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-msal/python-msal.changes 2020-06-17 14:51:17.369176790 +0200 +++ /work/SRC/openSUSE:Factory/.python-msal.new.4249/python-msal.changes 2020-10-02 17:28:11.990412164 +0200 @@ -1,0 +2,29 @@ +Tue Sep 8 19:53:50 UTC 2020 - John Paul Adrian Glaubitz <[email protected]> + +- Update to version 1.5.0 + + Added support for setting client capabilities to enable + CAE(Continuous Access Evaluation) (#240, #174) + + Device code endpoint is now fetched from open-id configuration, + if available. (#245, #242) + + Fixes in test cases (#239, #211) + +------------------------------------------------------------------- +Fri Aug 28 13:29:30 UTC 2020 - John Paul Adrian Glaubitz <[email protected]> + +- Update to version 1.4.3 + + Bugfix: A side effect in previous release prevented reading some + tokens from a different authority alias (#235, #236) +- from version 1.4.2 + + Bugfix: Changed case of messageID in WS-Trust Requests (#228 , #230 ) + + Bugfix: Removed content-type header sent in request to Mex endpoint (#226 , #227 ) + + Bugfix: Bypasses cache lookup for authority alias if no refresh token found (#223, #225 ) +- from version 1.4.1 + + Reverts Application Initializer will not send network requests + introduced in MSAL Python 1.4.0 (#205, #216, #187) +- from version 1.4.0 + + Enhancement: Application initializer will not send network requests. (#205, #187) + + Enhancement: Improved handling of errors in ADAL to MSAL token migration scenario. (#209, #208) + + Added changelog in PYPI (#203, #202) + + Other readme and reference docs adjustments (#200, #197) + +------------------------------------------------------------------- Old: ---- msal-1.3.0.tar.gz New: ---- msal-1.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-msal.spec ++++++ --- /var/tmp/diff_new_pack.X05hpw/_old 2020-10-02 17:28:16.274414729 +0200 +++ /var/tmp/diff_new_pack.X05hpw/_new 2020-10-02 17:28:16.278414731 +0200 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-msal -Version: 1.3.0 +Version: 1.5.0 Release: 0 Summary: Microsoft Authentication Library (MSAL) for Python License: MIT ++++++ msal-1.3.0.tar.gz -> msal-1.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.3.0/PKG-INFO new/msal-1.5.0/PKG-INFO --- old/msal-1.3.0/PKG-INFO 2020-05-15 05:35:49.000000000 +0200 +++ new/msal-1.5.0/PKG-INFO 2020-09-03 23:50:40.000000000 +0200 @@ -1,11 +1,12 @@ Metadata-Version: 2.1 Name: msal -Version: 1.3.0 +Version: 1.5.0 Summary: The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect. Home-page: https://github.com/AzureAD/microsoft-authentication-library-for-python Author: Microsoft Corporation Author-email: [email protected] License: MIT +Project-URL: Changelog, https://github.com/AzureAD/microsoft-authentication-library-for-python/releases Description: # Microsoft Authentication Library (MSAL) for Python @@ -58,7 +59,7 @@ from msal import PublicClientApplication app = PublicClientApplication( "your_client_id", - "authority": "https://login.microsoftonline.com/Enter_the_Tenant_Name_Here") + authority="https://login.microsoftonline.com/Enter_the_Tenant_Name_Here") ``` Later, each time you would want an access token, you start by: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.3.0/README.md new/msal-1.5.0/README.md --- old/msal-1.3.0/README.md 2020-05-15 05:35:24.000000000 +0200 +++ new/msal-1.5.0/README.md 2020-09-03 23:50:17.000000000 +0200 @@ -50,7 +50,7 @@ from msal import PublicClientApplication app = PublicClientApplication( "your_client_id", - "authority": "https://login.microsoftonline.com/Enter_the_Tenant_Name_Here") + authority="https://login.microsoftonline.com/Enter_the_Tenant_Name_Here") ``` Later, each time you would want an access token, you start by: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.3.0/msal/application.py new/msal-1.5.0/msal/application.py --- old/msal-1.3.0/msal/application.py 2020-05-15 05:35:24.000000000 +0200 +++ new/msal-1.5.0/msal/application.py 2020-09-03 23:50:17.000000000 +0200 @@ -21,7 +21,7 @@ # The __init__.py will import this. Not the other way around. -__version__ = "1.3.0" +__version__ = "1.5.0" logger = logging.getLogger(__name__) @@ -79,9 +79,21 @@ return [public_cert_content.strip()] +def _merge_claims_challenge_and_capabilities(capabilities, claims_challenge): + # Represent capabilities as {"access_token": {"xms_cc": {"values": capabilities}}} + # and then merge/add it into incoming claims + if not capabilities: + return claims_challenge + claims_dict = json.loads(claims_challenge) if claims_challenge else {} + for key in ["access_token"]: # We could add "id_token" if we'd decide to + claims_dict.setdefault(key, {}).update(xms_cc={"values": capabilities}) + return json.dumps(claims_dict) + + class ClientApplication(object): ACQUIRE_TOKEN_SILENT_ID = "84" + ACQUIRE_TOKEN_BY_REFRESH_TOKEN = "85" ACQUIRE_TOKEN_BY_USERNAME_PASSWORD_ID = "301" ACQUIRE_TOKEN_ON_BEHALF_OF_ID = "523" ACQUIRE_TOKEN_BY_DEVICE_FLOW_ID = "622" @@ -96,7 +108,8 @@ token_cache=None, http_client=None, verify=True, proxies=None, timeout=None, - client_claims=None, app_name=None, app_version=None): + client_claims=None, app_name=None, app_version=None, + client_capabilities=None): """Create an instance of application. :param str client_id: Your app has a client_id after you register it on AAD. @@ -178,10 +191,16 @@ :param app_version: (optional) You can provide your application version for Microsoft telemetry purposes. Default value is None, means it will not be passed to Microsoft. + :param list[str] client_capabilities: (optional) + Allows configuration of one or more client capabilities, e.g. ["CP1"]. + MSAL will combine them into + `claims parameter <https://openid.net/specs/openid-connect-core-1_0-final.html#ClaimsParameter`_ + which you will later provide via one of the acquire-token request. """ self.client_id = client_id self.client_credential = client_credential self.client_claims = client_claims + self._client_capabilities = client_capabilities if http_client: self.http_client = http_client else: @@ -234,6 +253,7 @@ "authorization_endpoint": authority.authorization_endpoint, "token_endpoint": authority.token_endpoint, "device_authorization_endpoint": + authority.device_authorization_endpoint or urljoin(authority.token_endpoint, "devicecode"), } return Client( @@ -259,6 +279,7 @@ prompt=None, nonce=None, domain_hint=None, # type: Optional[str] + claims_challenge=None, **kwargs): """Constructs a URL for you to start a Authorization Code Grant. @@ -284,8 +305,15 @@ Can be one of "consumers" or "organizations" or your tenant domain "contoso.com". If included, it will skip the email-based discovery process that user goes through on the sign-in page, leading to a slightly more streamlined user experience. - https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oapx/86fb452d-e34a-494e-ac61-e526e263b6d8 + More information on possible values + `here <https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code>`_ and + `here <https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oapx/86fb452d-e34a-494e-ac61-e526e263b6d8>`_. + :param claims_challenge: + The claims_challenge parameter requests specific claims requested by the resource provider + in the form of a claims_challenge directive in the www-authenticate header to be + returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. + It is a string of a JSON object which contains lists of claims being requested from these locations. + :return: The authorization url as a string. """ """ # TBD: this would only be meaningful in a new acquire_token_interactive() @@ -318,6 +346,8 @@ scope=decorate_scope(scopes, self.client_id), nonce=nonce, domain_hint=domain_hint, + claims=_merge_claims_challenge_and_capabilities( + self._client_capabilities, claims_challenge), ) def acquire_token_by_authorization_code( @@ -329,6 +359,7 @@ # authorization request as described in Section 4.1.1, and their # values MUST be identical. nonce=None, + claims_challenge=None, **kwargs): """The second half of the Authorization Code Grant. @@ -354,6 +385,12 @@ same nonce should also be provided here, so that we'll validate it. An exception will be raised if the nonce in id token mismatches. + :param claims_challenge: + The claims_challenge parameter requests specific claims requested by the resource provider + in the form of a claims_challenge directive in the www-authenticate header to be + returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. + It is a string of a JSON object which contains lists of claims being requested from these locations. + :return: A dict representing the json response from AAD: - A successful response would contain "access_token" key, @@ -374,6 +411,10 @@ CLIENT_CURRENT_TELEMETRY: _build_current_telemetry_request_header( self.ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE_ID), }, + data=dict( + kwargs.pop("data", {}), + claims=_merge_claims_challenge_and_capabilities( + self._client_capabilities, claims_challenge)), nonce=nonce, **kwargs) @@ -476,6 +517,7 @@ account, # type: Optional[Account] authority=None, # See get_authorization_request_url() force_refresh=False, # type: Optional[boolean] + claims_challenge=None, **kwargs): """Acquire an access token for given account, without user interaction. @@ -490,6 +532,12 @@ Internally, this method calls :func:`~acquire_token_silent_with_error`. + :param claims_challenge: + The claims_challenge parameter requests specific claims requested by the resource provider + in the form of a claims_challenge directive in the www-authenticate header to be + returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. + It is a string of a JSON object which contains lists of claims being requested from these locations. + :return: - A dict containing no "error" key, and typically contains an "access_token" key, @@ -497,7 +545,8 @@ - None when cache lookup does not yield a token. """ result = self.acquire_token_silent_with_error( - scopes, account, authority, force_refresh, **kwargs) + scopes, account, authority, force_refresh, + claims_challenge=claims_challenge, **kwargs) return result if result and "error" not in result else None def acquire_token_silent_with_error( @@ -506,6 +555,7 @@ account, # type: Optional[Account] authority=None, # See get_authorization_request_url() force_refresh=False, # type: Optional[boolean] + claims_challenge=None, **kwargs): """Acquire an access token for given account, without user interaction. @@ -526,6 +576,11 @@ :param force_refresh: If True, it will skip Access Token look-up, and try to find a Refresh Token to obtain a new Access Token. + :param claims_challenge: + The claims_challenge parameter requests specific claims requested by the resource provider + in the form of a claims_challenge directive in the www-authenticate header to be + returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. + It is a string of a JSON object which contains lists of claims being requested from these locations. :return: - A dict containing no "error" key, and typically contains an "access_token" key, @@ -544,18 +599,28 @@ # ) if authority else self.authority result = self._acquire_token_silent_from_cache_and_possibly_refresh_it( scopes, account, self.authority, force_refresh=force_refresh, + claims_challenge=claims_challenge, correlation_id=correlation_id, **kwargs) if result and "error" not in result: return result final_result = result for alias in self._get_authority_aliases(self.authority.instance): + if not self.token_cache.find( + self.token_cache.CredentialType.REFRESH_TOKEN, + # target=scopes, # MUST NOT filter by scopes, because: + # 1. AAD RTs are scope-independent; + # 2. therefore target is optional per schema; + query={"environment": alias}): + # Skip heavy weight logic when RT for this alias doesn't exist + continue the_authority = Authority( "https://" + alias + "/" + self.authority.tenant, self.http_client, validate_authority=False) result = self._acquire_token_silent_from_cache_and_possibly_refresh_it( scopes, account, the_authority, force_refresh=force_refresh, + claims_challenge=claims_challenge, correlation_id=correlation_id, **kwargs) if result: @@ -578,8 +643,9 @@ account, # type: Optional[Account] authority, # This can be different than self.authority force_refresh=False, # type: Optional[boolean] + claims_challenge=None, **kwargs): - if not force_refresh: + if not (force_refresh or claims_challenge): # Bypass AT when desired or using claims query={ "client_id": self.client_id, "environment": authority.instance, @@ -606,7 +672,7 @@ } return self._acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family( authority, decorate_scope(scopes, self.client_id), account, - force_refresh=force_refresh, **kwargs) + force_refresh=force_refresh, claims_challenge=claims_challenge, **kwargs) def _acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family( self, authority, scopes, account, **kwargs): @@ -655,7 +721,7 @@ def _acquire_token_silent_by_finding_specific_refresh_token( self, authority, scopes, query, rt_remover=None, break_condition=lambda response: False, - force_refresh=False, correlation_id=None, **kwargs): + force_refresh=False, correlation_id=None, claims_challenge=None, **kwargs): matches = self.token_cache.find( self.token_cache.CredentialType.REFRESH_TOKEN, # target=scopes, # AAD RTs are scope-independent @@ -675,6 +741,10 @@ CLIENT_CURRENT_TELEMETRY: _build_current_telemetry_request_header( self.ACQUIRE_TOKEN_SILENT_ID, force_refresh=force_refresh), }, + data=dict( + kwargs.pop("data", {}), + claims=_merge_claims_challenge_and_capabilities( + self._client_capabilities, claims_challenge)), **kwargs) if "error" not in response: return response @@ -725,9 +795,15 @@ """ return self.client.obtain_token_by_refresh_token( refresh_token, - decorate_scope(scopes, self.client_id), + scope=decorate_scope(scopes, self.client_id), + headers={ + CLIENT_REQUEST_ID: _get_new_correlation_id(), + CLIENT_CURRENT_TELEMETRY: _build_current_telemetry_request_header( + self.ACQUIRE_TOKEN_BY_REFRESH_TOKEN), + }, rt_getter=lambda rt: rt, on_updating_rt=False, + on_removing_rt=lambda rt_item: None, # No OP ) @@ -763,7 +839,7 @@ flow[self.DEVICE_FLOW_CORRELATION_ID] = correlation_id return flow - def acquire_token_by_device_flow(self, flow, **kwargs): + def acquire_token_by_device_flow(self, flow, claims_challenge=None, **kwargs): """Obtain token by a device flow object, with customizable polling effect. :param dict flow: @@ -771,6 +847,11 @@ By default, this method's polling effect will block current thread. You can abort the polling loop at any time, by changing the value of the flow's "expires_at" key to 0. + :param claims_challenge: + The claims_challenge parameter requests specific claims requested by the resource provider + in the form of a claims_challenge directive in the www-authenticate header to be + returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. + It is a string of a JSON object which contains lists of claims being requested from these locations. :return: A dict representing the json response from AAD: @@ -779,10 +860,14 @@ """ return self.client.obtain_token_by_device_flow( flow, - data=dict(kwargs.pop("data", {}), code=flow["device_code"]), - # 2018-10-4 Hack: - # during transition period, - # service seemingly need both device_code and code parameter. + data=dict( + kwargs.pop("data", {}), + code=flow["device_code"], # 2018-10-4 Hack: + # during transition period, + # service seemingly need both device_code and code parameter. + claims=_merge_claims_challenge_and_capabilities( + self._client_capabilities, claims_challenge), + ), headers={ CLIENT_REQUEST_ID: flow.get(self.DEVICE_FLOW_CORRELATION_ID) or _get_new_correlation_id(), @@ -792,7 +877,7 @@ **kwargs) def acquire_token_by_username_password( - self, username, password, scopes, **kwargs): + self, username, password, scopes, claims_challenge=None, **kwargs): """Gets a token for a given resource via user credentials. See this page for constraints of Username Password Flow. @@ -802,6 +887,11 @@ :param str password: The password. :param list[str] scopes: Scopes requested to access a protected API (a resource). + :param claims_challenge: + The claims_challenge parameter requests specific claims requested by the resource provider + in the form of a claims_challenge directive in the www-authenticate header to be + returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. + It is a string of a JSON object which contains lists of claims being requested from these locations. :return: A dict representing the json response from AAD: @@ -814,16 +904,22 @@ CLIENT_CURRENT_TELEMETRY: _build_current_telemetry_request_header( self.ACQUIRE_TOKEN_BY_USERNAME_PASSWORD_ID), } + data = dict( + kwargs.pop("data", {}), + claims=_merge_claims_challenge_and_capabilities( + self._client_capabilities, claims_challenge)) if not self.authority.is_adfs: user_realm_result = self.authority.user_realm_discovery( username, correlation_id=headers[CLIENT_REQUEST_ID]) if user_realm_result.get("account_type") == "Federated": return self._acquire_token_by_username_password_federated( user_realm_result, username, password, scopes=scopes, + data=data, headers=headers, **kwargs) return self.client.obtain_token_by_username_password( username, password, scope=scopes, headers=headers, + data=data, **kwargs) def _acquire_token_by_username_password_federated( @@ -865,11 +961,16 @@ class ConfidentialClientApplication(ClientApplication): # server-side web app - def acquire_token_for_client(self, scopes, **kwargs): + def acquire_token_for_client(self, scopes, claims_challenge=None, **kwargs): """Acquires token for the current confidential client, not for an end user. :param list[str] scopes: (Required) Scopes requested to access a protected API (a resource). + :param claims_challenge: + The claims_challenge parameter requests specific claims requested by the resource provider + in the form of a claims_challenge directive in the www-authenticate header to be + returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. + It is a string of a JSON object which contains lists of claims being requested from these locations. :return: A dict representing the json response from AAD: @@ -884,9 +985,13 @@ CLIENT_CURRENT_TELEMETRY: _build_current_telemetry_request_header( self.ACQUIRE_TOKEN_FOR_CLIENT_ID), }, + data=dict( + kwargs.pop("data", {}), + claims=_merge_claims_challenge_and_capabilities( + self._client_capabilities, claims_challenge)), **kwargs) - def acquire_token_on_behalf_of(self, user_assertion, scopes, **kwargs): + def acquire_token_on_behalf_of(self, user_assertion, scopes, claims_challenge=None, **kwargs): """Acquires token using on-behalf-of (OBO) flow. The current app is a middle-tier service which was called with a token @@ -901,6 +1006,11 @@ :param str user_assertion: The incoming token already received by this app :param list[str] scopes: Scopes required by downstream API (a resource). + :param claims_challenge: + The claims_challenge parameter requests specific claims requested by the resource provider + in the form of a claims_challenge directive in the www-authenticate header to be + returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token. + It is a string of a JSON object which contains lists of claims being requested from these locations. :return: A dict representing the json response from AAD: @@ -918,7 +1028,11 @@ # 2. Requesting an IDT (which would otherwise be unavailable) # so that the calling app could use id_token_claims to implement # their own cache mapping, which is likely needed in web apps. - data=dict(kwargs.pop("data", {}), requested_token_use="on_behalf_of"), + data=dict( + kwargs.pop("data", {}), + requested_token_use="on_behalf_of", + claims=_merge_claims_challenge_and_capabilities( + self._client_capabilities, claims_challenge)), headers={ CLIENT_REQUEST_ID: _get_new_correlation_id(), CLIENT_CURRENT_TELEMETRY: _build_current_telemetry_request_header( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.3.0/msal/authority.py new/msal-1.5.0/msal/authority.py --- old/msal-1.3.0/msal/authority.py 2020-05-15 05:35:24.000000000 +0200 +++ new/msal-1.5.0/msal/authority.py 2020-09-03 23:50:17.000000000 +0200 @@ -92,6 +92,7 @@ logger.debug("openid_config = %s", openid_config) self.authorization_endpoint = openid_config['authorization_endpoint'] self.token_endpoint = openid_config['token_endpoint'] + self.device_authorization_endpoint = openid_config.get('device_authorization_endpoint') _, _, self.tenant = canonicalize(self.token_endpoint) # Usually a GUID self.is_adfs = self.tenant.lower() == 'adfs' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.3.0/msal/mex.py new/msal-1.5.0/msal/mex.py --- old/msal-1.3.0/msal/mex.py 2020-05-15 05:35:24.000000000 +0200 +++ new/msal-1.5.0/msal/mex.py 2020-09-03 23:50:17.000000000 +0200 @@ -41,9 +41,7 @@ def send_request(mex_endpoint, http_client, **kwargs): - mex_document = http_client.get( - mex_endpoint, headers={'Content-Type': 'application/soap+xml'}, - **kwargs).text + mex_document = http_client.get(mex_endpoint, **kwargs).text return Mex(mex_document).get_wstrust_username_password_endpoint() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.3.0/msal/oauth2cli/oauth2.py new/msal-1.5.0/msal/oauth2cli/oauth2.py --- old/msal-1.3.0/msal/oauth2cli/oauth2.py 2020-05-15 05:35:24.000000000 +0200 +++ new/msal-1.5.0/msal/oauth2cli/oauth2.py 2020-09-03 23:50:17.000000000 +0200 @@ -3,10 +3,10 @@ import json try: - from urllib.parse import urlencode, parse_qs + from urllib.parse import urlencode, parse_qs, quote_plus except ImportError: from urlparse import parse_qs - from urllib import urlencode + from urllib import urlencode, quote_plus import logging import warnings import time @@ -205,9 +205,14 @@ # client credentials in the request-body using the following # parameters: client_id, client_secret. if self.client_secret and self.client_id: - _headers["Authorization"] = "Basic " + base64.b64encode( - "{}:{}".format(self.client_id, self.client_secret) - .encode("ascii")).decode("ascii") + _headers["Authorization"] = "Basic " + base64.b64encode("{}:{}".format( + # Per https://tools.ietf.org/html/rfc6749#section-2.3.1 + # client_id and client_secret needs to be encoded by + # "application/x-www-form-urlencoded" + # https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 + # BEFORE they are fed into HTTP Basic Authentication + quote_plus(self.client_id), quote_plus(self.client_secret) + ).encode("ascii")).decode("ascii") if "token_endpoint" not in self.configuration: raise ValueError("token_endpoint not found in configuration") @@ -233,7 +238,7 @@ :param refresh_token: The refresh token issued to the client :param scope: If omitted, is treated as equal to the scope originally - granted by the resource ownser, + granted by the resource owner, according to https://tools.ietf.org/html/rfc6749#section-6 """ assert isinstance(refresh_token, string_types) @@ -397,7 +402,7 @@ def obtain_token_by_authorization_code( self, code, redirect_uri=None, scope=None, **kwargs): - """Get a token via auhtorization code. a.k.a. Authorization Code Grant. + """Get a token via authorization code. a.k.a. Authorization Code Grant. This is typically used by a server-side app (Confidential Client), but it can also be used by a device-side native app (Public Client). @@ -503,7 +508,7 @@ Either way, this token_item will be passed into other callbacks as-is. :param scope: If omitted, is treated as equal to the scope originally - granted by the resource ownser, + granted by the resource owner, according to https://tools.ietf.org/html/rfc6749#section-6 :param rt_getter: A callable to translate the token_item to a raw RT string :param on_removing_rt: If absent, fall back to the one defined in initialization diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.3.0/msal/oauth2cli/oidc.py new/msal-1.5.0/msal/oauth2cli/oidc.py --- old/msal-1.3.0/msal/oauth2cli/oidc.py 2020-05-15 05:35:24.000000000 +0200 +++ new/msal-1.5.0/msal/oauth2cli/oidc.py 2020-09-03 23:50:17.000000000 +0200 @@ -38,6 +38,12 @@ """ decoded = json.loads(decode_part(id_token.split('.')[1])) err = None # https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation + _now = now or time.time() + skew = 120 # 2 minutes + if _now + skew < decoded.get("nbf", _now - 1): # nbf is optional per JWT specs + # This is not an ID token validation, but a JWT validation + # https://tools.ietf.org/html/rfc7519#section-4.1.5 + err = "0. The ID token is not yet valid" if issuer and issuer != decoded["iss"]: # https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse err = ('2. The Issuer Identifier for the OpenID Provider, "%s", ' @@ -53,7 +59,7 @@ # the Client and the Token Endpoint (which it is in this flow), # the TLS server validation MAY be used to validate the issuer # in place of checking the token signature. - if (now or time.time()) > decoded["exp"]: + if _now > decoded["exp"]: err = "9. The current time MUST be before the time represented by the exp Claim." if nonce and nonce != decoded.get("nonce"): err = ("11. Nonce must be the same value " @@ -99,7 +105,7 @@ response_type, nonce=nonce, **kwargs) def obtain_token_by_authorization_code(self, code, nonce=None, **kwargs): - """Get a token via auhtorization code. a.k.a. Authorization Code Grant. + """Get a token via authorization code. a.k.a. Authorization Code Grant. Return value and all other parameters are the same as :func:`oauth2.Client.obtain_token_by_authorization_code`, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.3.0/msal/wstrust_request.py new/msal-1.5.0/msal/wstrust_request.py --- old/msal-1.3.0/msal/wstrust_request.py 2020-05-15 05:35:24.000000000 +0200 +++ new/msal-1.5.0/msal/wstrust_request.py 2020-09-03 23:50:17.000000000 +0200 @@ -79,7 +79,7 @@ return """<s:Envelope xmlns:s='{s}' xmlns:wsa='{wsa}' xmlns:wsu='{wsu}'> <s:Header> <wsa:Action s:mustUnderstand='1'>{soap_action}</wsa:Action> - <wsa:messageID>urn:uuid:{message_id}</wsa:messageID> + <wsa:MessageID>urn:uuid:{message_id}</wsa:MessageID> <wsa:ReplyTo> <wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address> </wsa:ReplyTo> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.3.0/msal.egg-info/PKG-INFO new/msal-1.5.0/msal.egg-info/PKG-INFO --- old/msal-1.3.0/msal.egg-info/PKG-INFO 2020-05-15 05:35:49.000000000 +0200 +++ new/msal-1.5.0/msal.egg-info/PKG-INFO 2020-09-03 23:50:40.000000000 +0200 @@ -1,11 +1,12 @@ Metadata-Version: 2.1 Name: msal -Version: 1.3.0 +Version: 1.5.0 Summary: The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect. Home-page: https://github.com/AzureAD/microsoft-authentication-library-for-python Author: Microsoft Corporation Author-email: [email protected] License: MIT +Project-URL: Changelog, https://github.com/AzureAD/microsoft-authentication-library-for-python/releases Description: # Microsoft Authentication Library (MSAL) for Python @@ -58,7 +59,7 @@ from msal import PublicClientApplication app = PublicClientApplication( "your_client_id", - "authority": "https://login.microsoftonline.com/Enter_the_Tenant_Name_Here") + authority="https://login.microsoftonline.com/Enter_the_Tenant_Name_Here") ``` Later, each time you would want an access token, you start by: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.3.0/setup.cfg new/msal-1.5.0/setup.cfg --- old/msal-1.3.0/setup.cfg 2020-05-15 05:35:49.000000000 +0200 +++ new/msal-1.5.0/setup.cfg 2020-09-03 23:50:40.000000000 +0200 @@ -1,6 +1,10 @@ [bdist_wheel] universal = 1 +[metadata] +project_urls = + Changelog = https://github.com/AzureAD/microsoft-authentication-library-for-python/releases + [egg_info] tag_build = tag_date = 0
