1
+ from . import oauth2
2
+ from .authority import Authority
3
+ from .request import decorate_scope
1
4
from .client_credential import ClientCredentialRequest
2
5
3
6
4
7
class ClientApplication (object ):
5
- DEFAULT_AUTHORITY = "https://login.microsoftonline.com/common/"
6
8
7
9
def __init__ (
8
10
self , client_id ,
9
- validate_authority = True , authority = DEFAULT_AUTHORITY ):
11
+ authority_url = "https://login.microsoftonline.com/common/" ,
12
+ validate_authority = True ):
10
13
self .client_id = client_id
11
- self .validate_authority = validate_authority
12
- self .authority = authority
13
- # def aquire_token_silent(
14
- # self, scopes, user=None, authority=None, policy=None,
15
- # force_refresh=False):
16
- # pass
14
+ self .authority = Authority (authority_url , validate_authority )
17
15
16
+ def acquire_token_silent (
17
+ self , scope ,
18
+ user = None , # It can be a string as user id, or a User object
19
+ authority = None , # See get_authorization_request_url()
20
+ policy = '' ,
21
+ force_refresh = False , # To force refresh an Access Token (not a RT)
22
+ ** kwargs ):
23
+ a = Authority (authority ) if authority else self .authority
24
+ client = oauth2 .Client (self .client_id , token_endpoint = a .token_endpoint )
25
+ 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 )
31
+ # TODO: refresh the refresh_token
32
+ return response
18
33
19
- class PublicClientApplication (ClientApplication ):
20
- DEFAULT_REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob"
21
34
22
- def __init__ (self , client_id , redirect_uri = DEFAULT_REDIRECT_URI , ** kwargs ):
23
- super (PublicClientApplication , self ).__init__ (client_id , ** kwargs )
24
- self .redirect_uri = redirect_uri
35
+ class PublicClientApplication (ClientApplication ): # browser app or mobile app
25
36
26
- class ConfidentialClientApplication (ClientApplication ):
27
- def __init__ (self , client_id , client_credential , user_token_cache , ** kwargs ):
37
+ ## TBD: what if redirect_uri is not needed in the constructor at all?
38
+ ## Device Code flow does not need redirect_uri anyway.
39
+
40
+ # OUT_OF_BAND = "urn:ietf:wg:oauth:2.0:oob"
41
+ # def __init__(self, client_id, redirect_uri=None, **kwargs):
42
+ # super(PublicClientApplication, self).__init__(client_id, **kwargs)
43
+ # self.redirect_uri = redirect_uri or self.OUT_OF_BAND
44
+
45
+ def acquire_token (
46
+ self ,
47
+ scope ,
48
+ # additional_scope=None, # See also get_authorization_request_url()
49
+ login_hint = None ,
50
+ ui_options = None ,
51
+ # user=None, # TBD: It exists in MSAL-dotnet but not in MSAL-Android
52
+ policy = '' ,
53
+ authority = None , # See get_authorization_request_url()
54
+ extra_query_params = None ,
55
+ ):
56
+ # It will handle the TWO round trips of Authorization Code Grant flow.
57
+ raise NotImplemented ()
58
+
59
+ # TODO: Support Device Code flow
60
+
61
+
62
+ class ConfidentialClientApplication (ClientApplication ): # server-side web app
63
+ def __init__ (
64
+ self , client_id , client_credential , user_token_cache = None ,
65
+ # redirect_uri=None, # Experimental: Removed for now.
66
+ # acquire_token_for_client() doesn't need it
67
+ ** kwargs ):
28
68
"""
29
69
:param client_credential: It can be a string containing client secret,
30
- or an X509 certificate object.
70
+ or an X509 certificate container in this form:
71
+
72
+ {
73
+ "certificate": "-----BEGIN PRIVATE KEY-----...",
74
+ "thumbprint": "A1B2C3D4E5F6...",
75
+ }
31
76
"""
32
77
super (ConfidentialClientApplication , self ).__init__ (client_id , ** kwargs )
33
78
self .client_credential = client_credential
@@ -37,5 +82,84 @@ def __init__(self, client_id, client_credential, user_token_cache, **kwargs):
37
82
def acquire_token_for_client (self , scope , policy = '' ):
38
83
return ClientCredentialRequest (
39
84
client_id = self .client_id , client_credential = self .client_credential ,
40
- scope = scope , policy = policy , authority = self .authority ).run ()
85
+ scope = scope , # This grant flow requires no scope decoration
86
+ policy = policy , authority = self .authority ).run ()
87
+
88
+ def get_authorization_request_url (
89
+ self ,
90
+ scope ,
91
+ # additional_scope=None, # Not yet implemented
92
+ login_hint = None ,
93
+ state = None , # Recommended by OAuth2 for CSRF protection
94
+ policy = '' ,
95
+ redirect_uri = None ,
96
+ authority = None , # By default, it will use self.authority;
97
+ # Multi-tenant app can use new authority on demand
98
+ extra_query_params = None , # None or a dictionary
99
+ ):
100
+ """Constructs a URL for you to start a Authorization Code Grant.
101
+
102
+ :param scope: Scope refers to the resource that will be used in the
103
+ resulting token's audience.
104
+ :param additional_scope: Additional scope is a concept only in AAD.
105
+ It refers to other resources you might want to prompt to consent
106
+ for in the same interaction, but for which you won't get back a
107
+ token for in this particular operation.
108
+ (Under the hood, we simply merge scope and additional_scope before
109
+ sending them on the wire.)
110
+ :param str state: Recommended by OAuth2 for CSRF protection.
111
+ """
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 (
116
+ redirect_uri = redirect_uri , state = state , login_hint = login_hint ,
117
+ scope = decorate_scope (scope , self .client_id , policy ),
118
+ policy = policy if policy else None ,
119
+ ** (extra_query_params or {}))
120
+
121
+ def acquire_token_by_authorization_code (
122
+ self ,
123
+ code ,
124
+ scope , # Syntactically required. STS accepts empty value though.
125
+ redirect_uri = None ,
126
+ # REQUIRED, if the "redirect_uri" parameter was included in the
127
+ # authorization request as described in Section 4.1.1, and their
128
+ # values MUST be identical.
129
+ policy = ''
130
+ ):
131
+ """The second half of the Authorization Code Grant.
132
+
133
+ :param code: The authorization code returned from Authorization Server.
134
+ :param scope:
135
+
136
+ If you requested user consent for multiple resources, here you will
137
+ typically want to provide a subset of what you required in AC.
138
+
139
+ OAuth2 was designed mostly for singleton services,
140
+ where tokens are always meant for the same resource and the only
141
+ changes are in the scopes.
142
+ In AAD, tokens can be issued for multiple 3rd parth resources.
143
+ You can ask authorization code for multiple resources,
144
+ but when you redeem it, the token is for only one intended
145
+ recipient, called audience.
146
+ So the developer need to specify a scope so that we can restrict the
147
+ token to be issued for the corresponding audience.
148
+ """
149
+ # If scope is absent on the wire, STS will give you a token associated
150
+ # to the FIRST scope sent during the authorization request.
151
+ # So in theory, you can omit scope here when you were working with only
152
+ # one scope. But, MSAL decorates your scope anyway, so they are never
153
+ # 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 )
161
+
162
+ def acquire_token_on_behalf_of (
163
+ self , user_assertion , scope , authority = None , policy = '' ):
164
+ pass
41
165
0 commit comments