Hello community,

here is the log from the commit of package python-msal for openSUSE:Factory 
checked in at 2020-06-17 14:50:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-msal (Old)
 and      /work/SRC/openSUSE:Factory/.python-msal.new.3606 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-msal"

Wed Jun 17 14:50:58 2020 rev:2 rq:815262 version:1.3.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-msal/python-msal.changes  2020-02-28 
15:19:57.949725105 +0100
+++ /work/SRC/openSUSE:Factory/.python-msal.new.3606/python-msal.changes        
2020-06-17 14:51:17.369176790 +0200
@@ -1,0 +2,28 @@
+Tue Jun 16 13:11:05 UTC 2020 - John Paul Adrian Glaubitz 
<adrian.glaub...@suse.com>
+
+- Update to version 1.3.0
+  + New feature: class ```ClientApplication``` accepts a new optional parameter
+    ```http_client```. You can provide your own HTTP client to have different
+    behavior. (#169) Please refer to API Reference doc.
+  + New feature: method ```get_authorization_request_url()``` accepts a new 
optional
+    parameter ```domain_hint```. (#158, #181)
+    Please refer to API Reference doc.
+  + New feature: A new method ```acquire_token_by_refresh_token()``` to help 
migrating
+    refresh tokens from elsewhere to MSAL Python. (#193)
+    Its usage is demonstrated in this sample.
+- from version 1.2.0
+  + New ```nonce``` parameter is provided in ```both 
get_authorization_request_url(..., nonce=...)```
+    and ```acquire_token_by_authorization_code(..., nonce=...)``` method, so
+    that you can use them to mitigate replay attacks, per OIDC specs. (#128, 
#173).
+- from version 1.1.0
+    + New ```acquire_token_silent_with_error(...)``` method to expose 
conditional
+      access error classifications (#143, closes #57).
+    + App developers can opt in to provide their app's name and version for 
Microsoft
+      Telemetry, so that we can understand your usage pattern and serve you 
better.
+      (#136 closes #130)
+    + Internally,
+      * Collect anonymous telemetry data to help us improve MSAL Python (#103)
+      * Test cases cover ADFS 2019 on-premise scenarios (#142, closes #132)
+      * Switched to our latest lab apis for better test infrastructure (#108, 
#133, #134, #135)
+
+-------------------------------------------------------------------

Old:
----
  msal-1.1.0.tar.gz

New:
----
  msal-1.3.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-msal.spec ++++++
--- /var/tmp/diff_new_pack.Na3XMR/_old  2020-06-17 14:51:18.209179667 +0200
+++ /var/tmp/diff_new_pack.Na3XMR/_new  2020-06-17 14:51:18.209179667 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-msal
-Version:        1.1.0
+Version:        1.3.0
 Release:        0
 Summary:        Microsoft Authentication Library (MSAL) for Python
 License:        MIT

++++++ msal-1.1.0.tar.gz -> msal-1.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/PKG-INFO new/msal-1.3.0/PKG-INFO
--- old/msal-1.1.0/PKG-INFO     2020-01-24 00:12:24.000000000 +0100
+++ new/msal-1.3.0/PKG-INFO     2020-05-15 05:35:49.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: msal
-Version: 1.1.0
+Version: 1.3.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
@@ -9,9 +9,9 @@
 Description: # Microsoft Authentication Library (MSAL) for Python
         
         
-        | `dev` branch | Reference Docs
-        |---------------|---------------
-         [![Build 
status](https://api.travis-ci.org/AzureAD/microsoft-authentication-library-for-python.svg?branch=dev)](https://travis-ci.org/AzureAD/microsoft-authentication-library-for-python)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
+        | `dev` branch | Reference Docs | # of Downloads
+        |---------------|---------------|----------------|
+         [![Build 
status](https://api.travis-ci.org/AzureAD/microsoft-authentication-library-for-python.svg?branch=dev)](https://travis-ci.org/AzureAD/microsoft-authentication-library-for-python)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
 | [![Download 
monthly](https://pepy.tech/badge/msal/month)](https://pypistats.org/packages/msal)
         
         The Microsoft Authentication Library for Python enables applications 
to integrate with the [Microsoft identity platform](https://aka.ms/aaddevv2). 
It allows you to sign in users or apps with Microsoft identities ([Azure 
AD](https://azure.microsoft.com/services/active-directory/), [Microsoft 
Accounts](https://account.microsoft.com) and [Azure AD 
B2C](https://azure.microsoft.com/services/active-directory-b2c/) accounts) and 
obtain tokens to call Microsoft APIs such as [Microsoft 
Graph](https://graph.microsoft.io/) or your own APIs registered with the 
Microsoft identity platform. It is built using industry standard OAuth2 and 
OpenID Connect protocols
         
@@ -43,6 +43,11 @@
         [register your application with the Microsoft identity 
platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-v2-register-an-app).
         
         Acquiring tokens with MSAL Python follows this 3-step pattern.
+        (Note: That is the high level conceptual pattern.
+        There will be some variations for different flows. They are 
demonstrated in
+        [runnable samples hosted right in this 
repo](https://github.com/AzureAD/microsoft-authentication-library-for-python/tree/dev/sample).
+        )
+        
         
         1. MSAL proposes a clean separation between
            [public client applications, and confidential client 
applications](https://tools.ietf.org/html/rfc6749#section-2.1).
@@ -51,7 +56,9 @@
         
            ```python
            from msal import PublicClientApplication
-           app = PublicClientApplication("your_client_id", authority="...")
+           app = PublicClientApplication(
+               "your_client_id",
+               "authority": 
"https://login.microsoftonline.com/Enter_the_Tenant_Name_Here";)
            ```
         
            Later, each time you would want an access token, you start by:
@@ -75,7 +82,7 @@
                # Assuming the end user chose this one
                chosen = accounts[0]
                # Now let's try to find a token in cache for this account
-               result = app.acquire_token_silent(config["scope"], 
account=chosen)
+               result = app.acquire_token_silent(["your_scope"], 
account=chosen)
            ```
         
         3. Either there is no suitable token in the cache, or you chose to 
skip the previous step,
@@ -94,9 +101,6 @@
                print(result.get("correlation_id"))  # You may need this when 
reporting a bug
            ```
         
-        That is the high level pattern. There will be some variations for 
different flows. They are demonstrated in
-        [samples hosted right in this 
repo](https://github.com/AzureAD/microsoft-authentication-library-for-python/tree/dev/sample).
-        
         Refer the 
[Wiki](https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki)
 pages for more details on the MSAL Python functionality and usage.
         
         ## Migrating from ADAL
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/README.md new/msal-1.3.0/README.md
--- old/msal-1.1.0/README.md    2020-01-24 00:11:59.000000000 +0100
+++ new/msal-1.3.0/README.md    2020-05-15 05:35:24.000000000 +0200
@@ -1,9 +1,9 @@
 # Microsoft Authentication Library (MSAL) for Python
 
 
-| `dev` branch | Reference Docs
-|---------------|---------------
- [![Build 
status](https://api.travis-ci.org/AzureAD/microsoft-authentication-library-for-python.svg?branch=dev)](https://travis-ci.org/AzureAD/microsoft-authentication-library-for-python)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
+| `dev` branch | Reference Docs | # of Downloads
+|---------------|---------------|----------------|
+ [![Build 
status](https://api.travis-ci.org/AzureAD/microsoft-authentication-library-for-python.svg?branch=dev)](https://travis-ci.org/AzureAD/microsoft-authentication-library-for-python)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
 | [![Download 
monthly](https://pepy.tech/badge/msal/month)](https://pypistats.org/packages/msal)
 
 The Microsoft Authentication Library for Python enables applications to 
integrate with the [Microsoft identity platform](https://aka.ms/aaddevv2). It 
allows you to sign in users or apps with Microsoft identities ([Azure 
AD](https://azure.microsoft.com/services/active-directory/), [Microsoft 
Accounts](https://account.microsoft.com) and [Azure AD 
B2C](https://azure.microsoft.com/services/active-directory-b2c/) accounts) and 
obtain tokens to call Microsoft APIs such as [Microsoft 
Graph](https://graph.microsoft.io/) or your own APIs registered with the 
Microsoft identity platform. It is built using industry standard OAuth2 and 
OpenID Connect protocols
 
@@ -35,6 +35,11 @@
 [register your application with the Microsoft identity 
platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-v2-register-an-app).
 
 Acquiring tokens with MSAL Python follows this 3-step pattern.
+(Note: That is the high level conceptual pattern.
+There will be some variations for different flows. They are demonstrated in
+[runnable samples hosted right in this 
repo](https://github.com/AzureAD/microsoft-authentication-library-for-python/tree/dev/sample).
+)
+
 
 1. MSAL proposes a clean separation between
    [public client applications, and confidential client 
applications](https://tools.ietf.org/html/rfc6749#section-2.1).
@@ -43,7 +48,9 @@
 
    ```python
    from msal import PublicClientApplication
-   app = PublicClientApplication("your_client_id", authority="...")
+   app = PublicClientApplication(
+       "your_client_id",
+       "authority": 
"https://login.microsoftonline.com/Enter_the_Tenant_Name_Here";)
    ```
 
    Later, each time you would want an access token, you start by:
@@ -67,7 +74,7 @@
        # Assuming the end user chose this one
        chosen = accounts[0]
        # Now let's try to find a token in cache for this account
-       result = app.acquire_token_silent(config["scope"], account=chosen)
+       result = app.acquire_token_silent(["your_scope"], account=chosen)
    ```
 
 3. Either there is no suitable token in the cache, or you chose to skip the 
previous step,
@@ -86,9 +93,6 @@
        print(result.get("correlation_id"))  # You may need this when reporting 
a bug
    ```
 
-That is the high level pattern. There will be some variations for different 
flows. They are demonstrated in
-[samples hosted right in this 
repo](https://github.com/AzureAD/microsoft-authentication-library-for-python/tree/dev/sample).
-
 Refer the 
[Wiki](https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki)
 pages for more details on the MSAL Python functionality and usage.
 
 ## Migrating from ADAL
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/msal/application.py 
new/msal-1.3.0/msal/application.py
--- old/msal-1.1.0/msal/application.py  2020-01-24 00:11:59.000000000 +0100
+++ new/msal-1.3.0/msal/application.py  2020-05-15 05:35:24.000000000 +0200
@@ -1,3 +1,5 @@
+import functools
+import json
 import time
 try:  # Python 2
     from urlparse import urljoin
@@ -19,7 +21,7 @@
 
 
 # The __init__.py will import this. Not the other way around.
-__version__ = "1.1.0"
+__version__ = "1.3.0"
 
 logger = logging.getLogger(__name__)
 
@@ -54,11 +56,11 @@
 CLIENT_CURRENT_TELEMETRY = 'x-client-current-telemetry'
 
 def _get_new_correlation_id():
-        return str(uuid.uuid4())
+    return str(uuid.uuid4())
 
 
 def _build_current_telemetry_request_header(public_api_id, 
force_refresh=False):
-        return "1|{},{}|".format(public_api_id, "1" if force_refresh else "0")
+    return "1|{},{}|".format(public_api_id, "1" if force_refresh else "0")
 
 
 def extract_certs(public_cert_content):
@@ -92,12 +94,14 @@
             self, client_id,
             client_credential=None, authority=None, validate_authority=True,
             token_cache=None,
+            http_client=None,
             verify=True, proxies=None, timeout=None,
             client_claims=None, app_name=None, app_version=None):
         """Create an instance of application.
 
-        :param client_id: Your app has a client_id after you register it on 
AAD.
-        :param client_credential:
+        :param str client_id: Your app has a client_id after you register it 
on AAD.
+
+        :param str client_credential:
             For :class:`PublicClientApplication`, you simply use `None` here.
             For :class:`ConfidentialClientApplication`,
             it can be a string containing client secret,
@@ -114,6 +118,17 @@
             which will be sent through 'x5c' JWT header only for
             subject name and issuer authentication to support cert auto rolls.
 
+            Per `specs <https://tools.ietf.org/html/rfc7515#section-4.1.6>`_,
+            "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.
+
         :param dict client_claims:
             *Added in version 0.5.0*:
             It is a dictionary of extra claims that would be signed by
@@ -139,18 +154,24 @@
         :param TokenCache cache:
             Sets the token cache used by this ClientApplication instance.
             By default, an in-memory cache will be created and used.
+        :param http_client: (optional)
+            Your implementation of abstract class HttpClient 
<msal.oauth2cli.http.http_client>
+            Defaults to a requests session instance
         :param verify: (optional)
             It will be passed to the
             `verify parameter in the underlying requests library
             
<http://docs.python-requests.org/en/v2.9.1/user/advanced/#ssl-cert-verification>`_
+            This does not apply if you have chosen to pass your own Http client
         :param proxies: (optional)
             It will be passed to the
             `proxies parameter in the underlying requests library
             
<http://docs.python-requests.org/en/v2.9.1/user/advanced/#proxies>`_
+            This does not apply if you have chosen to pass your own Http client
         :param timeout: (optional)
             It will be passed to the
             `timeout parameter in the underlying requests library
             
<http://docs.python-requests.org/en/v2.9.1/user/advanced/#timeouts>`_
+            This does not apply if you have chosen to pass your own Http client
         :param app_name: (optional)
             You can provide your application name for Microsoft telemetry 
purposes.
             Default value is None, means it will not be passed to Microsoft.
@@ -161,14 +182,21 @@
         self.client_id = client_id
         self.client_credential = client_credential
         self.client_claims = client_claims
-        self.verify = verify
-        self.proxies = proxies
-        self.timeout = timeout
+        if http_client:
+            self.http_client = http_client
+        else:
+            self.http_client = requests.Session()
+            self.http_client.verify = verify
+            self.http_client.proxies = proxies
+            # Requests, does not support session - wide timeout
+            # But you can patch that 
(https://github.com/psf/requests/issues/3341):
+            self.http_client.request = functools.partial(
+                self.http_client.request, timeout=timeout)
         self.app_name = app_name
         self.app_version = app_version
         self.authority = Authority(
                 authority or "https://login.microsoftonline.com/common/";,
-                validate_authority, verify=verify, proxies=proxies, 
timeout=timeout)
+                self.http_client, validate_authority=validate_authority)
             # Here the self.authority is not the same type as authority in 
input
         self.token_cache = token_cache or TokenCache()
         self.client = self._build_client(client_credential, self.authority)
@@ -211,14 +239,14 @@
         return Client(
             server_configuration,
             self.client_id,
+            http_client=self.http_client,
             default_headers=default_headers,
             default_body=default_body,
             client_assertion=client_assertion,
             client_assertion_type=client_assertion_type,
             on_obtaining_tokens=self.token_cache.add,
             on_removing_rt=self.token_cache.remove_rt,
-            on_updating_rt=self.token_cache.update_rt,
-            verify=self.verify, proxies=self.proxies, timeout=self.timeout)
+            on_updating_rt=self.token_cache.update_rt)
 
     def get_authorization_request_url(
             self,
@@ -229,6 +257,8 @@
             redirect_uri=None,
             response_type="code",  # Can be "token" if you use Implicit Grant
             prompt=None,
+            nonce=None,
+            domain_hint=None,  # type: Optional[str]
             **kwargs):
         """Constructs a URL for you to start a Authorization Code Grant.
 
@@ -247,6 +277,15 @@
             You will have to specify a value explicitly.
             Its valid values are defined in Open ID Connect specs
             https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
+        :param nonce:
+            A cryptographically random value used to mitigate replay attacks. 
See also
+            `OIDC specs 
<https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>`_.
+        :param domain_hint:
+            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
         :return: The authorization url as a string.
         """
         """ # TBD: this would only be meaningful in a new 
acquire_token_interactive()
@@ -265,17 +304,20 @@
         # Multi-tenant app can use new authority on demand
         the_authority = Authority(
             authority,
-            verify=self.verify, proxies=self.proxies, timeout=self.timeout,
+            self.http_client
             ) if authority else self.authority
 
         client = Client(
             {"authorization_endpoint": the_authority.authorization_endpoint},
-            self.client_id)
+            self.client_id,
+            http_client=self.http_client)
         return client.build_auth_request_uri(
             response_type=response_type,
             redirect_uri=redirect_uri, state=state, login_hint=login_hint,
             prompt=prompt,
             scope=decorate_scope(scopes, self.client_id),
+            nonce=nonce,
+            domain_hint=domain_hint,
             )
 
     def acquire_token_by_authorization_code(
@@ -286,6 +328,7 @@
                 # REQUIRED, if the "redirect_uri" parameter was included in the
                 # authorization request as described in Section 4.1.1, and 
their
                 # values MUST be identical.
+            nonce=None,
             **kwargs):
         """The second half of the Authorization Code Grant.
 
@@ -306,6 +349,11 @@
             So the developer need to specify a scope so that we can restrict 
the
             token to be issued for the corresponding audience.
 
+        :param nonce:
+            If you provided a nonce when calling 
:func:`get_authorization_request_url`,
+            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.
+
         :return: A dict representing the json response from AAD:
 
             - A successful response would contain "access_token" key,
@@ -326,6 +374,7 @@
                 CLIENT_CURRENT_TELEMETRY: 
_build_current_telemetry_request_header(
                     self.ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE_ID),
                 },
+            nonce=nonce,
             **kwargs)
 
     def get_accounts(self, username=None):
@@ -367,13 +416,12 @@
 
     def _get_authority_aliases(self, instance):
         if not self.authority_groups:
-            resp = requests.get(
+            resp = self.http_client.get(
                 
"https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https://login.microsoftonline.com/common/oauth2/authorize";,
-                headers={'Accept': 'application/json'},
-                verify=self.verify, proxies=self.proxies, timeout=self.timeout)
+                headers={'Accept': 'application/json'})
             resp.raise_for_status()
             self.authority_groups = [
-                set(group['aliases']) for group in resp.json()['metadata']]
+                set(group['aliases']) for group in 
json.loads(resp.text)['metadata']]
         for group in self.authority_groups:
             if instance in group:
                 return [alias for alias in group if alias != instance]
@@ -492,7 +540,7 @@
             warnings.warn("We haven't decided how/if this method will accept 
authority parameter")
         # the_authority = Authority(
         #     authority,
-        #     verify=self.verify, proxies=self.proxies, timeout=self.timeout,
+        #     self.http_client,
         #     ) if authority else self.authority
         result = self._acquire_token_silent_from_cache_and_possibly_refresh_it(
             scopes, account, self.authority, force_refresh=force_refresh,
@@ -504,8 +552,8 @@
         for alias in self._get_authority_aliases(self.authority.instance):
             the_authority = Authority(
                 "https://"; + alias + "/" + self.authority.tenant,
-                validate_authority=False,
-                verify=self.verify, proxies=self.proxies, timeout=self.timeout)
+                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,
                 correlation_id=correlation_id,
@@ -585,8 +633,9 @@
                 **kwargs)
             if at and "error" not in at:
                 return at
+        last_resp = None
         if app_metadata.get("family_id"):  # Meaning this app belongs to this 
family
-            at = self._acquire_token_silent_by_finding_specific_refresh_token(
+            last_resp = at = 
self._acquire_token_silent_by_finding_specific_refresh_token(
                 authority, scopes, dict(query, 
family_id=app_metadata["family_id"]),
                 **kwargs)
             if at and "error" not in at:
@@ -594,7 +643,8 @@
         # Either this app is an orphan, so we will naturally use its own RT;
         # or all attempts above have failed, so we fall back to non-foci 
behavior.
         return self._acquire_token_silent_by_finding_specific_refresh_token(
-            authority, scopes, dict(query, client_id=self.client_id), **kwargs)
+            authority, scopes, dict(query, client_id=self.client_id),
+            **kwargs) or last_resp
 
     def _get_app_metadata(self, environment):
         apps = self.token_cache.find(  # Use find(), rather than 
token_cache.get(...)
@@ -650,6 +700,36 @@
                     "you must include a string parameter named 'key_id' "
                     "which identifies the key in the 'req_cnf' argument.")
 
+    def acquire_token_by_refresh_token(self, refresh_token, scopes):
+        """Acquire token(s) based on a refresh token (RT) obtained from 
elsewhere.
+
+        You use this method only when you have old RTs from elsewhere,
+        and now you want to migrate them into MSAL.
+        Calling this method results in new tokens automatically storing into 
MSAL.
+
+        You do NOT need to use this method if you are already using MSAL.
+        MSAL maintains RT automatically inside its token cache,
+        and an access token can be retrieved
+        when you call :func:`~acquire_token_silent`.
+
+        :param str refresh_token: The old refresh token, as a string.
+
+        :param list scopes:
+            The scopes associate with this old RT.
+            Each scope needs to be in the Microsoft identity platform (v2) 
format.
+            See `Scopes not resources 
<https://docs.microsoft.com/en-us/azure/active-directory/develop/migrate-python-adal-msal#scopes-not-resources>`_.
+
+        :return:
+            * A dict contains "error" and some other keys, when error happened.
+            * A dict contains no "error" key means migration was successful.
+        """
+        return self.client.obtain_token_by_refresh_token(
+            refresh_token,
+            decorate_scope(scopes, self.client_id),
+            rt_getter=lambda rt: rt,
+            on_updating_rt=False,
+            )
+
 
 class PublicClientApplication(ClientApplication):  # browser app or mobile app
 
@@ -713,7 +793,7 @@
 
     def acquire_token_by_username_password(
             self, username, password, scopes, **kwargs):
-        """Gets a token for a given resource via user credentails.
+        """Gets a token for a given resource via user credentials.
 
         See this page for constraints of Username Password Flow.
         
https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki/Username-Password-Authentication
@@ -748,13 +828,11 @@
 
     def _acquire_token_by_username_password_federated(
             self, user_realm_result, username, password, scopes=None, 
**kwargs):
-        verify = kwargs.pop("verify", self.verify)
-        proxies = kwargs.pop("proxies", self.proxies)
         wstrust_endpoint = {}
         if user_realm_result.get("federation_metadata_url"):
             wstrust_endpoint = mex_send_request(
                 user_realm_result["federation_metadata_url"],
-                verify=verify, proxies=proxies)
+                self.http_client)
             if wstrust_endpoint is None:
                 raise ValueError("Unable to find wstrust endpoint from MEX. "
                     "This typically happens when attempting MSA accounts. "
@@ -766,7 +844,7 @@
             wstrust_endpoint.get("address",
                 # Fallback to an AAD supplied endpoint
                 user_realm_result.get("federation_active_auth_url")),
-            wstrust_endpoint.get("action"), verify=verify, proxies=proxies)
+            wstrust_endpoint.get("action"), self.http_client)
         if not ("token" in wstrust_result and "type" in wstrust_result):
             raise RuntimeError("Unsuccessful RSTR. %s" % wstrust_result)
         GRANT_TYPE_SAML1_1 = 'urn:ietf:params:oauth:grant-type:saml1_1-bearer'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/msal/authority.py 
new/msal-1.3.0/msal/authority.py
--- old/msal-1.1.0/msal/authority.py    2020-01-24 00:11:59.000000000 +0100
+++ new/msal-1.3.0/msal/authority.py    2020-05-15 05:35:24.000000000 +0200
@@ -1,10 +1,14 @@
+import json
 try:
     from urllib.parse import urlparse
 except ImportError:  # Fall back to Python 2
     from urlparse import urlparse
 import logging
 
+# Historically some customers patched this module-wide requests instance.
+# We keep it here for now. They will be removed in next major release.
 import requests
+import requests as _requests
 
 from .exceptions import MsalServiceError
 
@@ -25,6 +29,7 @@
     "b2clogin.de",
     ]
 
+
 class Authority(object):
     """This class represents an (already-validated) authority.
 
@@ -33,9 +38,12 @@
     """
     _domains_without_user_realm_discovery = set([])
 
-    def __init__(self, authority_url, validate_authority=True,
-            verify=True, proxies=None, timeout=None,
-            ):
+    @property
+    def http_client(self):  # Obsolete. We will remove this in next major 
release.
+        # A workaround: if module-wide requests is patched, we honor it.
+        return self._http_client if requests is _requests else requests
+
+    def __init__(self, authority_url, http_client, validate_authority=True):
         """Creates an authority instance, and also validates it.
 
         :param validate_authority:
@@ -44,9 +52,7 @@
             This parameter only controls whether an instance discovery will be
             performed.
         """
-        self.verify = verify
-        self.proxies = proxies
-        self.timeout = timeout
+        self._http_client = http_client
         authority, self.instance, tenant = canonicalize(authority_url)
         parts = authority.path.split('/')
         is_b2c = any(self.instance.endswith("." + d) for d in 
WELL_KNOWN_B2C_HOSTS) or (
@@ -56,7 +62,7 @@
             payload = instance_discovery(
                 "https://{}{}/oauth2/v2.0/authorize".format(
                     self.instance, authority.path),
-                verify=verify, proxies=proxies, timeout=timeout)
+                self.http_client)
             if payload.get("error") == "invalid_instance":
                 raise ValueError(
                     "invalid_instance: "
@@ -73,9 +79,16 @@
                     authority.path,  # In B2C scenario, it is "/tenant/policy"
                     "" if tenant == "adfs" else "/v2.0" # the AAD v2 endpoint
                     ))
-        openid_config = tenant_discovery(
-            tenant_discovery_endpoint,
-            verify=verify, proxies=proxies, timeout=timeout)
+        try:
+            openid_config = tenant_discovery(
+                tenant_discovery_endpoint,
+                self.http_client)
+        except ValueError:  # json.decoder.JSONDecodeError in Py3 subclasses 
this
+            raise ValueError(
+                "Unable to get authority configuration for {}. "
+                "Authority would typically be in a format of "
+                "https://login.microsoftonline.com/your_tenant_name".format(
+                authority_url))
         logger.debug("openid_config = %s", openid_config)
         self.authorization_endpoint = openid_config['authorization_endpoint']
         self.token_endpoint = openid_config['token_endpoint']
@@ -87,15 +100,14 @@
         # "federation_protocol", "cloud_audience_urn",
         # "federation_metadata_url", "federation_active_auth_url", etc.
         if self.instance not in 
self.__class__._domains_without_user_realm_discovery:
-            resp = response or requests.get(
+            resp = response or self.http_client.get(
                 
"https://{netloc}/common/userrealm/{username}?api-version=1.0".format(
                     netloc=self.instance, username=username),
-                headers={'Accept':'application/json',
-                         'client-request-id': correlation_id},
-                verify=self.verify, proxies=self.proxies, timeout=self.timeout)
+                headers={'Accept': 'application/json',
+                         'client-request-id': correlation_id},)
             if resp.status_code != 404:
                 resp.raise_for_status()
-                return resp.json()
+                return json.loads(resp.text)
             
self.__class__._domains_without_user_realm_discovery.add(self.instance)
         return {}  # This can guide the caller to fall back normal ROPC flow
 
@@ -113,20 +125,21 @@
             % authority_url)
     return authority, authority.hostname, parts[1]
 
-def instance_discovery(url, **kwargs):
-    return requests.get(  # Note: This URL seemingly returns V1 endpoint only
+def instance_discovery(url, http_client, **kwargs):
+    resp = http_client.get(  # Note: This URL seemingly returns V1 endpoint 
only
         'https://{}/common/discovery/instance'.format(
             WORLD_WIDE  # Historically using WORLD_WIDE. Could use 
self.instance too
                 # See 
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4.0.0/src/Microsoft.Identity.Client/Instance/AadInstanceDiscovery.cs#L101-L103
                 # and 
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4.0.0/src/Microsoft.Identity.Client/Instance/AadAuthority.cs#L19-L33
             ),
         params={'authorization_endpoint': url, 'api-version': '1.0'},
-        **kwargs).json()
+        **kwargs)
+    return json.loads(resp.text)
 
-def tenant_discovery(tenant_discovery_endpoint, **kwargs):
+def tenant_discovery(tenant_discovery_endpoint, http_client, **kwargs):
     # Returns Openid Configuration
-    resp = requests.get(tenant_discovery_endpoint, **kwargs)
-    payload = resp.json()
+    resp = http_client.get(tenant_discovery_endpoint, **kwargs)
+    payload = json.loads(resp.text)
     if 'authorization_endpoint' in payload and 'token_endpoint' in payload:
         return payload
     raise MsalServiceError(status_code=resp.status_code, **payload)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/msal/mex.py new/msal-1.3.0/msal/mex.py
--- old/msal-1.1.0/msal/mex.py  2020-01-24 00:11:59.000000000 +0100
+++ new/msal-1.3.0/msal/mex.py  2020-05-15 05:35:24.000000000 +0200
@@ -34,15 +34,14 @@
 except ImportError:
     from xml.etree import ElementTree as ET
 
-import requests
-
 
 def _xpath_of_root(route_to_leaf):
     # Construct an xpath suitable to find a root node which has a specified 
leaf
     return '/'.join(route_to_leaf + ['..'] * (len(route_to_leaf)-1))
 
-def send_request(mex_endpoint, **kwargs):
-    mex_document = requests.get(
+
+def send_request(mex_endpoint, http_client, **kwargs):
+    mex_document = http_client.get(
         mex_endpoint, headers={'Content-Type': 'application/soap+xml'},
         **kwargs).text
     return Mex(mex_document).get_wstrust_username_password_endpoint()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/msal/oauth2cli/http.py 
new/msal-1.3.0/msal/oauth2cli/http.py
--- old/msal-1.1.0/msal/oauth2cli/http.py       1970-01-01 01:00:00.000000000 
+0100
+++ new/msal-1.3.0/msal/oauth2cli/http.py       2020-05-15 05:35:24.000000000 
+0200
@@ -0,0 +1,69 @@
+"""This module documents the minimal http behaviors used by this package.
+
+Its interface is influenced by, and similar to a subset of some popular,
+real-world http libraries, such as requests, aiohttp and httpx.
+"""
+
+
+class HttpClient(object):
+    """This describes a minimal http request interface used by this package."""
+
+    def post(self, url, params=None, data=None, headers=None, **kwargs):
+        """HTTP post.
+
+        :param dict params: A dict to be url-encoded and sent as query-string.
+        :param dict headers: A dict representing headers to be sent via 
request.
+        :param data:
+            Implementation needs to support 2 types.
+
+            * A dict, which will need to be urlencode() before being sent.
+            * (Recommended) A string, which will be sent in request as-is.
+
+        It returns an :class:`~Response`-like object.
+
+        Note: In its async counterpart, this method would be defined as async.
+        """
+        return Response()
+
+    def get(self, url, params=None, headers=None, **kwargs):
+        """HTTP get.
+
+        :param dict params: A dict to be url-encoded and sent as query-string.
+        :param dict headers: A dict representing headers to be sent via 
request.
+
+        It returns an :class:`~Response`-like object.
+
+        Note: In its async counterpart, this method would be defined as async.
+        """
+        return Response()
+
+
+class Response(object):
+    """This describes a minimal http response interface used by this package.
+
+    :var int status_code:
+        The status code of this http response.
+
+        Our async code path would also accept an alias as "status".
+
+    :var string text:
+        The body of this http response.
+
+        Our async code path would also accept an awaitable with the same name.
+    """
+    status_code = 200  # Our async code path would also accept a name as 
"status"
+
+    text = "body as a string"  # Our async code path would also accept an 
awaitable
+        # We could define a json() method instead of a text property/method,
+        # but a `text` would be more generic,
+        # when downstream packages would potentially access some XML endpoints.
+
+    def raise_for_status(self):
+        """Raise an exception when http response status contains error"""
+        raise NotImplementedError("Your implementation should provide this")
+
+
+def _get_status_code(resp):
+    # RFC defines and some libraries use "status_code", others use "status"
+    return getattr(resp, "status_code", None) or resp.status
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/msal/oauth2cli/oauth2.py 
new/msal-1.3.0/msal/oauth2cli/oauth2.py
--- old/msal-1.1.0/msal/oauth2cli/oauth2.py     2020-01-24 00:11:59.000000000 
+0100
+++ new/msal-1.3.0/msal/oauth2cli/oauth2.py     2020-05-15 05:35:24.000000000 
+0200
@@ -1,6 +1,7 @@
 """This OAuth2 client implementation aims to be spec-compliant, and generic."""
 # OAuth2 spec https://tools.ietf.org/html/rfc6749
 
+import json
 try:
     from urllib.parse import urlencode, parse_qs
 except ImportError:
@@ -11,6 +12,7 @@
 import time
 import base64
 import sys
+import functools
 
 import requests
 
@@ -31,16 +33,28 @@
     CLIENT_ASSERTION_TYPE_SAML2 = 
"urn:ietf:params:oauth:client-assertion-type:saml2-bearer"
     client_assertion_encoders = {CLIENT_ASSERTION_TYPE_SAML2: 
encode_saml_assertion}
 
+    @property
+    def session(self):
+        warnings.warn("Will be gone in next major release", DeprecationWarning)
+        return self._http_client
+
+    @session.setter
+    def session(self, value):
+        warnings.warn("Will be gone in next major release", DeprecationWarning)
+        self._http_client = value
+
+
     def __init__(
             self,
             server_configuration,  # type: dict
             client_id,  # type: str
+            http_client=None,  # We insert it here to match the upcoming async 
API
             client_secret=None,  # type: Optional[str]
             client_assertion=None,  # type: Union[bytes, callable, None]
             client_assertion_type=None,  # type: Optional[str]
             default_headers=None,  # type: Optional[dict]
             default_body=None,  # type: Optional[dict]
-            verify=True,  # type: Union[str, True, False, None]
+            verify=None,  # type: Union[str, True, False, None]
             proxies=None,  # type: Optional[dict]
             timeout=None,  # type: Union[tuple, float, None]
             ):
@@ -57,6 +71,21 @@
                 or
                 https://example.com/.../.well-known/openid-configuration
             client_id (str): The client's id, issued by the authorization 
server
+
+            http_client (http.HttpClient):
+                Your implementation of abstract class :class:`http.HttpClient`.
+                Defaults to a requests session instance.
+
+                There is no session-wide `timeout` parameter defined here.
+                Timeout behavior is determined by the actual http client you 
use.
+                If you happen to use Requests, it disallows session-wide 
timeout
+                (https://github.com/psf/requests/issues/3341). The workaround 
is:
+
+                    s = requests.Session()
+                    s.request = functools.partial(s.request, timeout=3)
+
+                and then feed that patched session instance to this class.
+
             client_secret (str):  Triggers HTTP AUTH for Confidential Client
             client_assertion (bytes, callable):
                 The client assertion to authenticate this client, per RFC 7521.
@@ -76,20 +105,52 @@
                 you could choose to set this as {"client_secret": "your 
secret"}
                 if your authorization server wants it to be in the request body
                 (rather than in the request header).
+
+            verify (boolean):
+                It will be passed to the
+                `verify parameter in the underlying requests library
+                
<http://docs.python-requests.org/en/v2.9.1/user/advanced/#ssl-cert-verification>`_.
+                When leaving it with default value (None), we will use True 
instead.
+
+                This does not apply if you have chosen to pass your own Http 
client.
+
+            proxies (dict):
+                It will be passed to the
+                `proxies parameter in the underlying requests library
+                
<http://docs.python-requests.org/en/v2.9.1/user/advanced/#proxies>`_.
+
+                This does not apply if you have chosen to pass your own Http 
client.
+
+            timeout (object):
+                It will be passed to the
+                `timeout parameter in the underlying requests library
+                
<http://docs.python-requests.org/en/v2.9.1/user/advanced/#timeouts>`_.
+
+                This does not apply if you have chosen to pass your own Http 
client.
+
         """
         self.configuration = server_configuration
         self.client_id = client_id
         self.client_secret = client_secret
         self.client_assertion = client_assertion
+        self.default_headers = default_headers or {}
         self.default_body = default_body or {}
         if client_assertion_type is not None:
             self.default_body["client_assertion_type"] = client_assertion_type
         self.logger = logging.getLogger(__name__)
-        self.session = s = requests.Session()
-        s.headers.update(default_headers or {})
-        s.verify = verify
-        s.proxies = proxies or {}
-        self.timeout = timeout
+        if http_client:
+            if verify is not None or proxies is not None or timeout is not 
None:
+                raise ValueError(
+                    "verify, proxies, or timeout is not allowed "
+                    "when http_client is in use")
+            self._http_client = http_client
+        else:
+            self._http_client = requests.Session()
+            self._http_client.verify = True if verify is None else verify
+            self._http_client.proxies = proxies
+            self._http_client.request = functools.partial(
+                # A workaround for requests not supporting session-wide timeout
+                self._http_client.request, timeout=timeout)
 
     def _build_auth_request_params(self, response_type, **kwargs):
         # response_type is a string defined in
@@ -110,10 +171,9 @@
             params=None,  # a dict to be sent as query string to the endpoint
             data=None,  # All relevant data, which will go into the http body
             headers=None,  # a dict to be sent as request headers
-            timeout=None,
             post=None,  # A callable to replace requests.post(), for testing.
                         # Such as: lambda url, **kwargs:
-                        #   Mock(status_code=200, json=Mock(return_value={}))
+                        #   Mock(status_code=200, text='{}')
             **kwargs  # Relay all extra parameters to underlying requests
             ):  # Returns the json object came from the OAUTH2 response
         _data = {'client_id': self.client_id, 'grant_type': grant_type}
@@ -128,11 +188,15 @@
 
         _data.update(self.default_body)  # It may contain authen parameters
         _data.update(data or {})  # So the content in data param prevails
-        # We don't have to clean up None values here, because requests lib 
will.
+        _data = {k: v for k, v in _data.items() if v}  # Clean up None values
 
         if _data.get('scope'):
             _data['scope'] = self._stringify(_data['scope'])
 
+        _headers = {'Accept': 'application/json'}
+        _headers.update(self.default_headers)
+        _headers.update(headers or {})
+
         # Quoted from https://tools.ietf.org/html/rfc6749#section-2.3.1
         # Clients in possession of a client password MAY use the HTTP Basic
         # authentication.
@@ -140,18 +204,16 @@
         # the authorization server MAY support including the
         # client credentials in the request-body using the following
         # parameters: client_id, client_secret.
-        auth = None
         if self.client_secret and self.client_id:
-            auth = (self.client_id, self.client_secret)  # for HTTP Basic Auth
+            _headers["Authorization"] = "Basic " + base64.b64encode(
+                "{}:{}".format(self.client_id, self.client_secret)
+                .encode("ascii")).decode("ascii")
 
         if "token_endpoint" not in self.configuration:
             raise ValueError("token_endpoint not found in configuration")
-        _headers = {'Accept': 'application/json'}
-        _headers.update(headers or {})
-        resp = (post or self.session.post)(
+        resp = (post or self._http_client.post)(
             self.configuration["token_endpoint"],
-            headers=_headers, params=params, data=_data, auth=auth,
-            timeout=timeout or self.timeout,
+            headers=_headers, params=params, data=_data,
             **kwargs)
         if resp.status_code >= 500:
             resp.raise_for_status()  # TODO: Will probably retry here
@@ -159,7 +221,7 @@
             # The spec (https://tools.ietf.org/html/rfc6749#section-5.2) says
             # even an error response will be a valid json structure,
             # so we simply return it here, without needing to invent an 
exception.
-            return resp.json()
+            return json.loads(resp.text)
         except ValueError:
             self.logger.exception(
                     "Token response is not in json format: %s", resp.text)
@@ -200,7 +262,7 @@
     grant_assertion_encoders = {GRANT_TYPE_SAML2: 
BaseClient.encode_saml_assertion}
 
 
-    def initiate_device_flow(self, scope=None, timeout=None, **kwargs):
+    def initiate_device_flow(self, scope=None, **kwargs):
         # type: (list, **dict) -> dict
         # The naming of this method is following the wording of this specs
         # 
https://tools.ietf.org/html/draft-ietf-oauth-device-flow-12#section-3.1
@@ -218,10 +280,11 @@
         DAE = "device_authorization_endpoint"
         if not self.configuration.get(DAE):
             raise ValueError("You need to provide device authorization 
endpoint")
-        flow = self.session.post(self.configuration[DAE],
+        resp = self._http_client.post(self.configuration[DAE],
             data={"client_id": self.client_id, "scope": self._stringify(scope 
or [])},
-            timeout=timeout or self.timeout,
-            **kwargs).json()
+            headers=dict(self.default_headers, **kwargs.pop("headers", {})),
+            **kwargs)
+        flow = json.loads(resp.text)
         flow["interval"] = int(flow.get("interval", 5))  # Some IdP returns 
string
         flow["expires_in"] = int(flow.get("expires_in", 1800))
         flow["expires_at"] = time.time() + flow["expires_in"]  # We invent this
@@ -391,17 +454,20 @@
         self.on_removing_rt = on_removing_rt
         self.on_updating_rt = on_updating_rt
 
-    def _obtain_token(self, grant_type, params=None, data=None, *args, 
**kwargs):
-        RT = "refresh_token"
+    def _obtain_token(
+            self, grant_type, params=None, data=None,
+            also_save_rt=False,
+            *args, **kwargs):
         _data = data.copy()  # to prevent side effect
-        refresh_token = _data.get(RT)
         resp = super(Client, self)._obtain_token(
             grant_type, params, _data, *args, **kwargs)
         if "error" not in resp:
             _resp = resp.copy()
-            if grant_type == RT and RT in _resp and isinstance(refresh_token, 
dict):
-                _resp.pop(RT)  # So we skip it in on_obtaining_tokens(); it 
will
-                               # be handled in 
self.obtain_token_by_refresh_token()
+            RT = "refresh_token"
+            if grant_type == RT and RT in _resp and not also_save_rt:
+                # Then we skip it from on_obtaining_tokens();
+                # Leave it to self.obtain_token_by_refresh_token()
+                _resp.pop(RT, None)
             if "scope" in _resp:
                 scope = _resp["scope"].split()  # It is conceptually a set,
                     # but we represent it as a list which can be persisted to 
JSON
@@ -423,6 +489,7 @@
     def obtain_token_by_refresh_token(self, token_item, scope=None,
             rt_getter=lambda token_item: token_item["refresh_token"],
             on_removing_rt=None,
+            on_updating_rt=None,
             **kwargs):
         # type: (Union[str, dict], Union[str, list, set, tuple], Callable) -> 
dict
         """This is an overload which will trigger token storage callbacks.
@@ -440,16 +507,28 @@
             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
+
+        :param on_updating_rt:
+            Default to None, it will fall back to the one defined in 
initialization.
+            This is the most common case.
+
+            As a special case, you can pass in a False,
+            then this function will NOT trigger on_updating_rt() for RT UPDATE,
+            instead it will allow the RT to be added by on_obtaining_tokens().
+            This behavior is useful when you are migrating RTs from elsewhere
+            into a token storage managed by this library.
         """
         resp = super(Client, self).obtain_token_by_refresh_token(
             rt_getter(token_item)
                 if not isinstance(token_item, string_types) else token_item,
             scope=scope,
+            also_save_rt=on_updating_rt is False,
             **kwargs)
         if resp.get('error') == 'invalid_grant':
             (on_removing_rt or self.on_removing_rt)(token_item)  # Discard old 
RT
-        if 'refresh_token' in resp:
-            self.on_updating_rt(token_item, resp['refresh_token'])
+        RT = "refresh_token"
+        if on_updating_rt is not False and RT in resp:
+            (on_updating_rt or self.on_updating_rt)(token_item, resp[RT])
         return resp
 
     def obtain_token_by_assertion(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/msal/oauth2cli/oidc.py 
new/msal-1.3.0/msal/oauth2cli/oidc.py
--- old/msal-1.1.0/msal/oauth2cli/oidc.py       2020-01-24 00:11:59.000000000 
+0100
+++ new/msal-1.3.0/msal/oauth2cli/oidc.py       2020-05-15 05:35:24.000000000 
+0200
@@ -85,3 +85,37 @@
             ret["id_token_claims"] = self.decode_id_token(ret["id_token"])
         return ret
 
+    def build_auth_request_uri(self, response_type, nonce=None, **kwargs):
+        """Generate an authorization uri to be visited by resource owner.
+
+        Return value and all other parameters are the same as
+        :func:`oauth2.Client.build_auth_request_uri`, plus new parameter(s):
+
+        :param nonce:
+            A hard-to-guess string used to mitigate replay attacks. See also
+            `OIDC specs 
<https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>`_.
+        """
+        return super(Client, self).build_auth_request_uri(
+            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.
+
+        Return value and all other parameters are the same as
+        :func:`oauth2.Client.obtain_token_by_authorization_code`,
+        plus new parameter(s):
+
+        :param nonce:
+            If you provided a nonce when calling 
:func:`build_auth_request_uri`,
+            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.
+        """
+        result = super(Client, self).obtain_token_by_authorization_code(
+            code, **kwargs)
+        nonce_in_id_token = result.get("id_token_claims", {}).get("nonce")
+        if "id_token_claims" in result and nonce and nonce != 
nonce_in_id_token:
+            raise ValueError(
+                'The nonce in id token ("%s") should match your nonce ("%s")' %
+                (nonce_in_id_token, nonce))
+        return result
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/msal/wstrust_request.py 
new/msal-1.3.0/msal/wstrust_request.py
--- old/msal-1.1.0/msal/wstrust_request.py      2020-01-24 00:11:59.000000000 
+0100
+++ new/msal-1.3.0/msal/wstrust_request.py      2020-05-15 05:35:24.000000000 
+0200
@@ -29,16 +29,13 @@
 from datetime import datetime, timedelta
 import logging
 
-import requests
-
 from .mex import Mex
 from .wstrust_response import parse_response
 
-
 logger = logging.getLogger(__name__)
 
 def send_request(
-        username, password, cloud_audience_urn, endpoint_address, soap_action,
+        username, password, cloud_audience_urn, endpoint_address, soap_action, 
http_client,
         **kwargs):
     if not endpoint_address:
         raise ValueError("WsTrust endpoint address can not be empty")
@@ -51,7 +48,7 @@
         "Unsupported soap action: %s" % soap_action)
     data = _build_rst(
         username, password, cloud_audience_urn, endpoint_address, soap_action)
-    resp = requests.post(endpoint_address, data=data, headers={
+    resp = http_client.post(endpoint_address, data=data, headers={
             'Content-type':'application/soap+xml; charset=utf-8',
             'SOAPAction': soap_action,
             }, **kwargs)
@@ -61,11 +58,13 @@
     # resp.raise_for_status()
     return parse_response(resp.text)
 
+
 def escape_password(password):
     return (password.replace('&', '&amp;').replace('"', '&quot;')
         .replace("'", '&apos;')  # the only one not provided by cgi.escape(s, 
True)
         .replace('<', '&lt;').replace('>', '&gt;'))
 
+
 def wsu_time_format(datetime_obj):
     # WsTrust (http://docs.oasis-open.org/ws-sx/ws-trust/v1.4/ws-trust.html)
     # does not seem to define timestamp format, but we see YYYY-mm-ddTHH:MM:SSZ
@@ -74,6 +73,7 @@
     # 
https://docs.python.org/2/library/datetime.html#datetime.datetime.isoformat
     return datetime_obj.strftime('%Y-%m-%dT%H:%M:%SZ')
 
+
 def _build_rst(username, password, cloud_audience_urn, endpoint_address, 
soap_action):
     now = datetime.utcnow()
     return """<s:Envelope xmlns:s='{s}' xmlns:wsa='{wsa}' xmlns:wsu='{wsu}'>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/msal.egg-info/PKG-INFO 
new/msal-1.3.0/msal.egg-info/PKG-INFO
--- old/msal-1.1.0/msal.egg-info/PKG-INFO       2020-01-24 00:12:24.000000000 
+0100
+++ new/msal-1.3.0/msal.egg-info/PKG-INFO       2020-05-15 05:35:49.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: msal
-Version: 1.1.0
+Version: 1.3.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
@@ -9,9 +9,9 @@
 Description: # Microsoft Authentication Library (MSAL) for Python
         
         
-        | `dev` branch | Reference Docs
-        |---------------|---------------
-         [![Build 
status](https://api.travis-ci.org/AzureAD/microsoft-authentication-library-for-python.svg?branch=dev)](https://travis-ci.org/AzureAD/microsoft-authentication-library-for-python)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
+        | `dev` branch | Reference Docs | # of Downloads
+        |---------------|---------------|----------------|
+         [![Build 
status](https://api.travis-ci.org/AzureAD/microsoft-authentication-library-for-python.svg?branch=dev)](https://travis-ci.org/AzureAD/microsoft-authentication-library-for-python)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
 | [![Download 
monthly](https://pepy.tech/badge/msal/month)](https://pypistats.org/packages/msal)
         
         The Microsoft Authentication Library for Python enables applications 
to integrate with the [Microsoft identity platform](https://aka.ms/aaddevv2). 
It allows you to sign in users or apps with Microsoft identities ([Azure 
AD](https://azure.microsoft.com/services/active-directory/), [Microsoft 
Accounts](https://account.microsoft.com) and [Azure AD 
B2C](https://azure.microsoft.com/services/active-directory-b2c/) accounts) and 
obtain tokens to call Microsoft APIs such as [Microsoft 
Graph](https://graph.microsoft.io/) or your own APIs registered with the 
Microsoft identity platform. It is built using industry standard OAuth2 and 
OpenID Connect protocols
         
@@ -43,6 +43,11 @@
         [register your application with the Microsoft identity 
platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-v2-register-an-app).
         
         Acquiring tokens with MSAL Python follows this 3-step pattern.
+        (Note: That is the high level conceptual pattern.
+        There will be some variations for different flows. They are 
demonstrated in
+        [runnable samples hosted right in this 
repo](https://github.com/AzureAD/microsoft-authentication-library-for-python/tree/dev/sample).
+        )
+        
         
         1. MSAL proposes a clean separation between
            [public client applications, and confidential client 
applications](https://tools.ietf.org/html/rfc6749#section-2.1).
@@ -51,7 +56,9 @@
         
            ```python
            from msal import PublicClientApplication
-           app = PublicClientApplication("your_client_id", authority="...")
+           app = PublicClientApplication(
+               "your_client_id",
+               "authority": 
"https://login.microsoftonline.com/Enter_the_Tenant_Name_Here";)
            ```
         
            Later, each time you would want an access token, you start by:
@@ -75,7 +82,7 @@
                # Assuming the end user chose this one
                chosen = accounts[0]
                # Now let's try to find a token in cache for this account
-               result = app.acquire_token_silent(config["scope"], 
account=chosen)
+               result = app.acquire_token_silent(["your_scope"], 
account=chosen)
            ```
         
         3. Either there is no suitable token in the cache, or you chose to 
skip the previous step,
@@ -94,9 +101,6 @@
                print(result.get("correlation_id"))  # You may need this when 
reporting a bug
            ```
         
-        That is the high level pattern. There will be some variations for 
different flows. They are demonstrated in
-        [samples hosted right in this 
repo](https://github.com/AzureAD/microsoft-authentication-library-for-python/tree/dev/sample).
-        
         Refer the 
[Wiki](https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki)
 pages for more details on the MSAL Python functionality and usage.
         
         ## Migrating from ADAL
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.1.0/msal.egg-info/SOURCES.txt 
new/msal-1.3.0/msal.egg-info/SOURCES.txt
--- old/msal-1.1.0/msal.egg-info/SOURCES.txt    2020-01-24 00:12:24.000000000 
+0100
+++ new/msal-1.3.0/msal.egg-info/SOURCES.txt    2020-05-15 05:35:49.000000000 
+0200
@@ -17,5 +17,6 @@
 msal/oauth2cli/__init__.py
 msal/oauth2cli/assertion.py
 msal/oauth2cli/authcode.py
+msal/oauth2cli/http.py
 msal/oauth2cli/oauth2.py
 msal/oauth2cli/oidc.py
\ No newline at end of file


Reply via email to