Skip to content

Commit 82e8eda

Browse files
committed
Implemented Username/Password grant for federated user
1 parent ea28a54 commit 82e8eda

File tree

2 files changed

+51
-2
lines changed

2 files changed

+51
-2
lines changed

msal/application.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
except: # Python 3
55
from urllib.parse import urljoin
66
import logging
7+
from base64 import b64encode
78

89
from oauth2cli import Client
910
from .authority import Authority
1011
from .assertion import create_jwt_assertion
12+
import mex
13+
import wstrust_request
14+
from .wstrust_response import SAML_TOKEN_TYPE_V1, SAML_TOKEN_TYPE_V2
1115
from .token_cache import TokenCache
1216

1317

18+
logger = logging.getLogger(__name__)
19+
1420
def decorate_scope(
1521
scope, client_id,
1622
policy=None, # obsolete
@@ -268,9 +274,40 @@ class PublicClientApplication(ClientApplication): # browser app or mobile app
268274
def acquire_token_with_username_password(
269275
self, username, password, scope=None, **kwargs):
270276
"""Gets a token for a given resource via user credentails."""
277+
scope = decorate_scope(scope, self.client_id)
278+
if not self.authority.is_adfs:
279+
user_realm_result = self.authority.user_realm_discovery(username)
280+
if user_realm_result.get("account_type") == "Federated":
281+
return self._acquire_token_with_username_password_federated(
282+
user_realm_result, username, password, scope=scope, **kwargs)
271283
return self.client.obtain_token_with_username_password(
272-
username, password,
273-
scope=decorate_scope(scope, self.client_id), **kwargs)
284+
username, password, scope=scope, **kwargs)
285+
286+
def _acquire_token_with_username_password_federated(
287+
self, user_realm_result, username, password, scope=None, **kwargs):
288+
wstrust_endpoint = {}
289+
if user_realm_result.get("federation_metadata_url"):
290+
wstrust_endpoint = mex.send_request(
291+
user_realm_result["federation_metadata_url"])
292+
logger.debug("wstrust_endpoint = %s", wstrust_endpoint)
293+
wstrust_result = wstrust_request.send_request(
294+
username, password, user_realm_result.get("cloud_audience_urn"),
295+
wstrust_endpoint.get("address",
296+
# Fallback to an AAD supplied endpoint
297+
user_realm_result.get("federation_active_auth_url")),
298+
wstrust_endpoint.get("action"), **kwargs)
299+
if not ("token" in wstrust_result and "type" in wstrust_result):
300+
raise RuntimeError("Unsuccessful RSTR. %s" % wstrust_result)
301+
grant_type = {
302+
SAML_TOKEN_TYPE_V1: 'urn:ietf:params:oauth:grant-type:saml1_1-bearer',
303+
SAML_TOKEN_TYPE_V2: self.client.GRANT_TYPE_SAML2,
304+
}.get(wstrust_result.get("type"))
305+
if not grant_type:
306+
raise RuntimeError(
307+
"RSTR returned unknown token type: %s", wstrust_result.get("type"))
308+
return self.client.obtain_token_with_assertion(
309+
b64encode(wstrust_result["token"]),
310+
grant_type=grant_type, scope=scope, **kwargs)
274311

275312
def acquire_token(
276313
self,

msal/authority.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ def __init__(self, authority_url, validate_authority=True):
4040
self.authorization_endpoint = openid_config['authorization_endpoint']
4141
self.token_endpoint = openid_config['token_endpoint']
4242
_, _, self.tenant = canonicalize(self.token_endpoint) # Usually a GUID
43+
self.is_adfs = self.tenant.lower() == 'adfs'
44+
45+
def user_realm_discovery(self, username, **kwargs):
46+
resp = requests.get(
47+
"https://{netloc}/common/userrealm/{username}?api-version=1.0".format(
48+
netloc=self.instance, username=username),
49+
headers={'Accept':'application/json'}, **kwargs)
50+
resp.raise_for_status()
51+
return resp.json()
52+
# It will typically contain "ver", "account_type",
53+
# "federation_protocol", "cloud_audience_urn",
54+
# "federation_metadata_url", "federation_active_auth_url", etc.
4355

4456
def canonicalize(url):
4557
# Returns (canonicalized_url, netloc, tenant). Raises ValueError on errors.

0 commit comments

Comments
 (0)