Skip to content

Commit 7ff35d7

Browse files
committed
Merge branch 'crossroads' into dev
2 parents 51d7ba1 + 89746d3 commit 7ff35d7

File tree

5 files changed

+156
-151
lines changed

5 files changed

+156
-151
lines changed

msal/application.py

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
1-
from . import oauth2
1+
from .oauth2 import Client
22
from .authority import Authority
33
from .request import decorate_scope
4-
from .client_credential import ClientCredentialRequest
4+
from .assertion import create_jwt_assertion
55

66

77
class ClientApplication(object):
88

99
def __init__(
1010
self, client_id,
11-
authority_url="https://login.microsoftonline.com/common/",
11+
authority="https://login.microsoftonline.com/common/",
1212
validate_authority=True):
1313
self.client_id = client_id
14-
self.authority = Authority(authority_url, validate_authority)
14+
self.authority = Authority(authority, validate_authority)
15+
# Here the self.authority is not the same type as authority in input
16+
17+
@staticmethod
18+
def _build_auth_parameters(client_credential, token_endpoint, client_id):
19+
if isinstance(client_credential, dict):
20+
type_ = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
21+
assertion = create_jwt_assertion(
22+
client_credential['certificate'],
23+
client_credential['thumbprint'],
24+
audience=token_endpoint, issuer=client_id)
25+
return {
26+
'client_assertion_type': type_, 'client_assertion': assertion}
27+
else:
28+
return {'client_secret': client_credential}
1529

1630
def acquire_token_silent(
1731
self, scope,
@@ -20,14 +34,17 @@ def acquire_token_silent(
2034
policy='',
2135
force_refresh=False, # To force refresh an Access Token (not a RT)
2236
**kwargs):
23-
a = Authority(authority) if authority else self.authority
24-
client = oauth2.Client(self.client_id, token_endpoint=a.token_endpoint)
37+
the_authority = Authority(authority) if authority else self.authority
2538
refresh_token = kwargs.get('refresh_token') # For testing purpose
26-
response = client.get_token_by_refresh_token(
27-
refresh_token,
28-
scope=decorate_scope(scope, self.client_id, policy),
29-
client_secret=getattr(self, 'client_credential'), # TODO: JWT too
30-
query={'policy': policy} if policy else None)
39+
response = Client(
40+
self.client_id, token_endpoint=the_authority.token_endpoint,
41+
default_body=self._build_auth_parameters(
42+
self.client_credential,
43+
the_authority.token_endpoint, self.client_id)
44+
).acquire_token_with_refresh_token(
45+
refresh_token,
46+
scope=decorate_scope(scope, self.client_id, policy),
47+
query={'p': policy} if policy else None)
3148
# TODO: refresh the refresh_token
3249
return response
3350

@@ -79,11 +96,15 @@ def __init__(
7996
self.user_token_cache = user_token_cache
8097
self.app_token_cache = None # TODO
8198

82-
def acquire_token_for_client(self, scope, policy=''):
83-
return ClientCredentialRequest(
84-
client_id=self.client_id, client_credential=self.client_credential,
85-
scope=scope, # This grant flow requires no scope decoration
86-
policy=policy, authority=self.authority).run()
99+
def acquire_token_for_client(self, scope, policy=None):
100+
token_endpoint = self.authority.token_endpoint
101+
return Client(
102+
self.client_id, token_endpoint=token_endpoint,
103+
default_body=self._build_auth_parameters(
104+
self.client_credential, token_endpoint, self.client_id)
105+
).acquire_token_with_client_credentials(
106+
scope=scope, # This grant flow requires no scope decoration
107+
query={'p': policy} if policy else None)
87108

88109
def get_authorization_request_url(
89110
self,
@@ -109,16 +130,18 @@ def get_authorization_request_url(
109130
sending them on the wire.)
110131
:param str state: Recommended by OAuth2 for CSRF protection.
111132
"""
112-
a = Authority(authority) if authority else self.authority
113-
grant = oauth2.AuthorizationCodeGrant(
114-
self.client_id, authorization_endpoint=a.authorization_endpoint)
115-
return grant.authorization_url(
133+
the_authority = Authority(authority) if authority else self.authority
134+
client = Client(
135+
self.client_id,
136+
authorization_endpoint=the_authority.authorization_endpoint)
137+
return client.authorization_url(
138+
response_type="code", # Using Authorization Code grant
116139
redirect_uri=redirect_uri, state=state, login_hint=login_hint,
117140
scope=decorate_scope(scope, self.client_id, policy),
118141
policy=policy if policy else None,
119142
**(extra_query_params or {}))
120143

121-
def acquire_token_by_authorization_code(
144+
def acquire_token_with_authorization_code(
122145
self,
123146
code,
124147
scope, # Syntactically required. STS accepts empty value though.
@@ -151,13 +174,15 @@ def acquire_token_by_authorization_code(
151174
# So in theory, you can omit scope here when you were working with only
152175
# one scope. But, MSAL decorates your scope anyway, so they are never
153176
# really empty.
154-
grant = oauth2.AuthorizationCodeGrant(
155-
self.client_id, token_endpoint=self.authority.token_endpoint)
156-
return grant.get_token(
157-
code, redirect_uri=redirect_uri,
158-
scope=decorate_scope(scope, self.client_id, policy),
159-
client_secret=self.client_credential, # TODO: Support certificate
160-
query={'policy': policy} if policy else None)
177+
return Client(
178+
self.client_id, token_endpoint=self.authority.token_endpoint,
179+
default_body=self._build_auth_parameters(
180+
self.client_credential,
181+
self.authority.token_endpoint, self.client_id)
182+
).acquire_token_with_authorization_code(
183+
code, redirect_uri=redirect_uri,
184+
scope=decorate_scope(scope, self.client_id, policy),
185+
query={'p': policy} if policy else None)
161186

162187
def acquire_token_on_behalf_of(
163188
self, user_assertion, scope, authority=None, policy=''):

msal/assertion.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import time
2+
import binascii
3+
import base64
4+
import uuid
5+
6+
import jwt
7+
8+
9+
def create_jwt_assertion(
10+
private_pem, thumbprint, audience, issuer,
11+
subject=None, # If None is specified, the value of issuer will be used
12+
not_valid_before=None, # If None, the current time will be used
13+
jwt_id=None): # If None is specified, a UUID will be generated
14+
assert '-----BEGIN PRIVATE KEY-----' in private_pem, "Need a standard PEM"
15+
nbf = time.time() if not_valid_before is None else not_valid_before
16+
payload = { # key names are all from JWT standard names
17+
'aud': audience,
18+
'iss': issuer,
19+
'sub': subject or issuer,
20+
'nbf': nbf,
21+
'exp': nbf + 10*60, # 10 minutes
22+
'jti': str(uuid.uuid4()) if jwt_id is None else jwt_id,
23+
}
24+
# Per http://self-issued.info/docs/draft-jones-json-web-token-01.html
25+
h = {'x5t': base64.urlsafe_b64encode(binascii.a2b_hex(thumbprint)).decode()}
26+
return jwt.encode(payload, private_pem, algorithm='RS256', headers=h)
27+

msal/client_credential.py

Lines changed: 0 additions & 56 deletions
This file was deleted.

0 commit comments

Comments
 (0)