Skip to content

Commit 4e2ed20

Browse files
authored
Merge pull request #233 from AzureAD/release-1.4.2
MSAL Python 1.4.2
2 parents d85a11b + d473cb0 commit 4e2ed20

File tree

7 files changed

+89
-20
lines changed

7 files changed

+89
-20
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve
4+
title: ''
5+
labels: ''
6+
assignees: ''
7+
8+
---
9+
10+
**Describe the bug**
11+
A clear and concise description of what the bug is.
12+
13+
**To Reproduce**
14+
Steps to reproduce the behavior:
15+
1. Go to our [off-the-shelf samples](https://github.com/AzureAD/microsoft-authentication-library-for-python/tree/dev/sample) and pick one that is closest to your usage scenario. You should not need to modify the sample.
16+
2. Follow the description of the sample, typically at the beginning of it, to prepare a `config.json` containing your test configurations
17+
3. Run such sample, typically by `python sample.py config.json`
18+
4. See the error
19+
5. In this bug report, tell us the sample you choose, paste the content of the config.json with your test setup (which you can choose to skip your credentials, and/or mail it to our developer's email).
20+
21+
**Expected behavior**
22+
A clear and concise description of what you expected to happen.
23+
24+
**What you see instead**
25+
Paste the sample output, or add screenshots to help explain your problem.
26+
27+
**The MSAL Python version you are using**
28+
Paste the output of this
29+
`python -c "import msal; print(msal.__version__)"`
30+
31+
**Additional context**
32+
Add any other context about the problem here.

msal/application.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222

2323
# The __init__.py will import this. Not the other way around.
24-
__version__ = "1.4.1"
24+
__version__ = "1.4.2"
2525

2626
logger = logging.getLogger(__name__)
2727

@@ -82,6 +82,7 @@ def extract_certs(public_cert_content):
8282
class ClientApplication(object):
8383

8484
ACQUIRE_TOKEN_SILENT_ID = "84"
85+
ACQUIRE_TOKEN_BY_REFRESH_TOKEN = "85"
8586
ACQUIRE_TOKEN_BY_USERNAME_PASSWORD_ID = "301"
8687
ACQUIRE_TOKEN_ON_BEHALF_OF_ID = "523"
8788
ACQUIRE_TOKEN_BY_DEVICE_FLOW_ID = "622"
@@ -551,6 +552,12 @@ def acquire_token_silent_with_error(
551552
return result
552553
final_result = result
553554
for alias in self._get_authority_aliases(self.authority.instance):
555+
if not self.token_cache.find(
556+
self.token_cache.CredentialType.REFRESH_TOKEN,
557+
target=scopes,
558+
query={"environment": alias}):
559+
# Skip heavy weight logic when RT for this alias doesn't exist
560+
continue
554561
the_authority = Authority(
555562
"https://" + alias + "/" + self.authority.tenant,
556563
self.http_client,
@@ -727,6 +734,11 @@ def acquire_token_by_refresh_token(self, refresh_token, scopes):
727734
return self.client.obtain_token_by_refresh_token(
728735
refresh_token,
729736
scope=decorate_scope(scopes, self.client_id),
737+
headers={
738+
CLIENT_REQUEST_ID: _get_new_correlation_id(),
739+
CLIENT_CURRENT_TELEMETRY: _build_current_telemetry_request_header(
740+
self.ACQUIRE_TOKEN_BY_REFRESH_TOKEN),
741+
},
730742
rt_getter=lambda rt: rt,
731743
on_updating_rt=False,
732744
on_removing_rt=lambda rt_item: None, # No OP

msal/mex.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,7 @@ def _xpath_of_root(route_to_leaf):
4141

4242

4343
def send_request(mex_endpoint, http_client, **kwargs):
44-
mex_document = http_client.get(
45-
mex_endpoint, headers={'Content-Type': 'application/soap+xml'},
46-
**kwargs).text
44+
mex_document = http_client.get(mex_endpoint, **kwargs).text
4745
return Mex(mex_document).get_wstrust_username_password_endpoint()
4846

4947

msal/oauth2cli/oauth2.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
import json
55
try:
6-
from urllib.parse import urlencode, parse_qs
6+
from urllib.parse import urlencode, parse_qs, quote_plus
77
except ImportError:
88
from urlparse import parse_qs
9-
from urllib import urlencode
9+
from urllib import urlencode, quote_plus
1010
import logging
1111
import warnings
1212
import time
@@ -205,9 +205,14 @@ def _obtain_token( # The verb "obtain" is influenced by OAUTH2 RFC 6749
205205
# client credentials in the request-body using the following
206206
# parameters: client_id, client_secret.
207207
if self.client_secret and self.client_id:
208-
_headers["Authorization"] = "Basic " + base64.b64encode(
209-
"{}:{}".format(self.client_id, self.client_secret)
210-
.encode("ascii")).decode("ascii")
208+
_headers["Authorization"] = "Basic " + base64.b64encode("{}:{}".format(
209+
# Per https://tools.ietf.org/html/rfc6749#section-2.3.1
210+
# client_id and client_secret needs to be encoded by
211+
# "application/x-www-form-urlencoded"
212+
# https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
213+
# BEFORE they are fed into HTTP Basic Authentication
214+
quote_plus(self.client_id), quote_plus(self.client_secret)
215+
).encode("ascii")).decode("ascii")
211216

212217
if "token_endpoint" not in self.configuration:
213218
raise ValueError("token_endpoint not found in configuration")

msal/oauth2cli/oidc.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ def decode_id_token(id_token, client_id=None, issuer=None, nonce=None, now=None)
3838
"""
3939
decoded = json.loads(decode_part(id_token.split('.')[1]))
4040
err = None # https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
41+
_now = now or time.time()
42+
if _now < decoded.get("nbf", _now - 1): # nbf is optional per JWT specs
43+
# This is not an ID token validation, but a JWT validation
44+
# https://tools.ietf.org/html/rfc7519#section-4.1.5
45+
err = "0. The ID token is not yet valid"
4146
if issuer and issuer != decoded["iss"]:
4247
# https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
4348
err = ('2. The Issuer Identifier for the OpenID Provider, "%s", '
@@ -53,7 +58,7 @@ def decode_id_token(id_token, client_id=None, issuer=None, nonce=None, now=None)
5358
# the Client and the Token Endpoint (which it is in this flow),
5459
# the TLS server validation MAY be used to validate the issuer
5560
# in place of checking the token signature.
56-
if (now or time.time()) > decoded["exp"]:
61+
if _now > decoded["exp"]:
5762
err = "9. The current time MUST be before the time represented by the exp Claim."
5863
if nonce and nonce != decoded.get("nonce"):
5964
err = ("11. Nonce must be the same value "

msal/wstrust_request.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def _build_rst(username, password, cloud_audience_urn, endpoint_address, soap_ac
7979
return """<s:Envelope xmlns:s='{s}' xmlns:wsa='{wsa}' xmlns:wsu='{wsu}'>
8080
<s:Header>
8181
<wsa:Action s:mustUnderstand='1'>{soap_action}</wsa:Action>
82-
<wsa:messageID>urn:uuid:{message_id}</wsa:messageID>
82+
<wsa:MessageID>urn:uuid:{message_id}</wsa:MessageID>
8383
<wsa:ReplyTo>
8484
<wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
8585
</wsa:ReplyTo>

tests/test_e2e.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def get_lab_app(
302302
# or it could be setup on Travis CI
303303
# https://docs.travis-ci.com/user/environment-variables/#defining-variables-in-repository-settings
304304
# Data came from here
305-
# https://microsoft.sharepoint-df.com/teams/MSIDLABSExtended/SitePages/Rese.aspx#programmatic-access-info-for-lab-request-api
305+
# https://docs.msidlab.com/accounts/confidentialclient.html
306306
logger.info("Using lab app defined by ENV variables %s and %s",
307307
env_client_id, env_client_secret)
308308
client_id = os.getenv(env_client_id)
@@ -482,7 +482,6 @@ def test_ropc_adfs2019_onprem(self):
482482
# Configuration is derived from https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4.7.0/tests/Microsoft.Identity.Test.Common/TestConstants.cs#L250-L259
483483
config = self.get_lab_user(usertype="onprem", federationProvider="ADFSv2019")
484484
config["authority"] = "https://fs.%s.com/adfs" % config["lab_name"]
485-
config["client_id"] = "PublicClientId"
486485
config["scope"] = self.adfs2019_scopes
487486
config["password"] = self.get_lab_user_secret(config["lab_name"])
488487
self._test_username_password(**config)
@@ -497,26 +496,31 @@ def test_adfs2019_onprem_acquire_token_by_auth_code(self):
497496
"""
498497
config = self.get_lab_user(usertype="onprem", federationProvider="ADFSv2019")
499498
config["authority"] = "https://fs.%s.com/adfs" % config["lab_name"]
500-
config["client_id"] = "PublicClientId"
501499
config["scope"] = self.adfs2019_scopes
502500
config["port"] = 8080
503501
self._test_acquire_token_by_auth_code(**config)
504502

505503
@unittest.skipUnless(
506504
os.getenv("LAB_OBO_CLIENT_SECRET"),
507505
"Need LAB_OBO_CLIENT SECRET from https://msidlabs.vault.azure.net/secrets/TodoListServiceV2-OBO/c58ba97c34ca4464886943a847d1db56")
506+
@unittest.skipUnless(
507+
os.getenv("LAB_OBO_CONFIDENTIAL_CLIENT_ID"),
508+
"Confidential client id can be found here https://docs.msidlab.com/flows/onbehalfofflow.html")
509+
@unittest.skipUnless(
510+
os.getenv("LAB_OBO_PUBLIC_CLIENT_ID"),
511+
"Public client id can be found here https://docs.msidlab.com/flows/onbehalfofflow.html")
508512
def test_acquire_token_obo(self):
509513
config = self.get_lab_user(usertype="cloud")
510514

511515
config_cca = {}
512516
config_cca.update(config)
513-
config_cca["client_id"] = "f4aa5217-e87c-42b2-82af-5624dd14ee72"
517+
config_cca["client_id"] = os.getenv("LAB_OBO_CONFIDENTIAL_CLIENT_ID")
514518
config_cca["scope"] = ["https://graph.microsoft.com/.default"]
515519
config_cca["client_secret"] = os.getenv("LAB_OBO_CLIENT_SECRET")
516520

517521
config_pca = {}
518522
config_pca.update(config)
519-
config_pca["client_id"] = "c0485386-1e9a-4663-bc96-7ab30656de7f"
523+
config_pca["client_id"] = os.getenv("LAB_OBO_PUBLIC_CLIENT_ID")
520524
config_pca["password"] = self.get_lab_user_secret(config_pca["lab_name"])
521525
config_pca["scope"] = ["api://%s/read" % config_cca["client_id"]]
522526

@@ -535,20 +539,22 @@ def test_b2c_acquire_token_by_auth_code(self):
535539
# This won't work https://msidlab.com/api/user?usertype=b2c
536540
password="***" # From https://aka.ms/GetLabUserSecret?Secret=msidlabb2c
537541
"""
542+
config = self.get_lab_app_object(azureenvironment="azureb2ccloud")
538543
self._test_acquire_token_by_auth_code(
539544
authority=self._build_b2c_authority("B2C_1_SignInPolicy"),
540-
client_id="b876a048-55a5-4fc5-9403-f5d90cb1c852",
545+
client_id=config["appId"],
541546
port=3843, # Lab defines 4 of them: [3843, 4584, 4843, 60000]
542-
scope=["https://msidlabb2c.onmicrosoft.com/msaapp/user_impersonation"]
547+
scope=config["defaultScopes"].split(','),
543548
)
544549

545550
def test_b2c_acquire_token_by_ropc(self):
551+
config = self.get_lab_app_object(azureenvironment="azureb2ccloud")
546552
self._test_username_password(
547553
authority=self._build_b2c_authority("B2C_1_ROPC_Auth"),
548-
client_id="e3b9ad76-9763-4827-b088-80c7a7888f79",
554+
client_id=config["appId"],
549555
username="[email protected]",
550556
password=self.get_lab_user_secret("msidlabb2c"),
551-
scope=["https://msidlabb2c.onmicrosoft.com/msidlabb2capi/read"],
557+
scope=config["defaultScopes"].split(','),
552558
)
553559

554560

@@ -584,6 +590,17 @@ def test_acquire_token_device_flow(self):
584590
config["scope"] = ["user.read"]
585591
self._test_device_flow(**config)
586592

593+
def test_acquire_token_silent_with_an_empty_cache_should_return_none(self):
594+
config = self.get_lab_user(
595+
usertype="cloud", azureenvironment=self.environment, publicClient="no")
596+
app = msal.ConfidentialClientApplication(
597+
config['client_id'], authority=config['authority'],
598+
http_client=MinimalHttpClient())
599+
result = app.acquire_token_silent(scopes=config['scope'], account=None)
600+
self.assertEqual(result, None)
601+
# Note: An alias in this region is no longer accepting HTTPS traffic.
602+
# If this test case passes without exception,
603+
# it means MSAL Python is not affected by that.
587604

588605
if __name__ == "__main__":
589606
unittest.main()

0 commit comments

Comments
 (0)