20
20
'authorization_code' : (Application .GRANT_ALLINONE , Application .GRANT_AUTHORIZATION_CODE ),
21
21
'password' : (Application .GRANT_ALLINONE , Application .GRANT_PASSWORD ),
22
22
'client_credentials' : (Application .GRANT_ALLINONE , Application .GRANT_CLIENT_CREDENTIALS ),
23
- 'refresh_token' : (Application .GRANT_ALLINONE , Application .GRANT_AUTHORIZATION_CODE , Application . GRANT_PASSWORD ,
24
- Application .GRANT_CLIENT_CREDENTIALS )
23
+ 'refresh_token' : (Application .GRANT_ALLINONE , Application .GRANT_AUTHORIZATION_CODE ,
24
+ Application .GRANT_PASSWORD , Application . GRANT_CLIENT_CREDENTIALS )
25
25
}
26
26
27
27
28
28
class OAuth2Validator (RequestValidator ):
29
- def _authenticate_basic_auth (self , request ):
29
+ def _extract_basic_auth (self , request ):
30
30
"""
31
- Authenticates with HTTP Basic Auth
31
+ Return authentication string if request contains basic auth credentials, else return None
32
32
"""
33
33
auth = request .headers .get ('HTTP_AUTHORIZATION' , None )
34
-
35
34
if not auth :
36
- return False
35
+ return None
37
36
38
37
auth_type , auth_string = auth .split (' ' )
39
38
if auth_type != "Basic" :
39
+ return None
40
+
41
+ return auth_string
42
+
43
+ def _authenticate_basic_auth (self , request ):
44
+ """
45
+ Authenticates with HTTP Basic Auth.
46
+
47
+ Note: as stated in rfc:`2.3.1`, client_id and client_secret must be encoded with
48
+ "application/x-www-form-urlencoded" encoding algorithm.
49
+ """
50
+ auth_string = self ._extract_basic_auth (request )
51
+ if not auth_string :
40
52
return False
41
53
42
54
encoding = request .encoding or 'utf-8'
@@ -45,38 +57,83 @@ def _authenticate_basic_auth(self, request):
45
57
client_id , client_secret = auth_string_decoded .split (':' , 1 )
46
58
47
59
try :
48
- request .client = Application .objects .get (client_id = unquote_plus (client_id ), client_secret = unquote_plus (client_secret ))
60
+ request .client = Application .objects .get (client_id = unquote_plus (client_id ),
61
+ client_secret = unquote_plus (client_secret ))
49
62
return True
50
63
51
64
except Application .DoesNotExist :
65
+ log .debug ("Failed basic auth: Application %s do not exists" % unquote_plus (client_id ))
52
66
return False
53
67
54
68
def _authenticate_request_body (self , request ):
55
69
"""
56
70
Try to authenticate the client using client_id and client_secret parameters
57
71
included in body.
58
72
59
- Remember that this method is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize
60
- the HTTP Basic authentication scheme. See rfc:`2.3.1` for more details.
73
+ Remember that this method is NOT RECOMMENDED and SHOULD be limited to clients unable to
74
+ directly utilize the HTTP Basic authentication scheme. See rfc:`2.3.1` for more details.
61
75
"""
62
76
client_id = request .client_id
63
77
client_secret = request .client_secret
64
78
65
- if not client_id :
79
+ if not client_id or not client_secret :
66
80
return False
67
81
68
82
try :
69
- request .client = Application .objects .get (client_id = client_id , client_secret = client_secret )
83
+ request .client = Application .objects .get (client_id = client_id ,
84
+ client_secret = client_secret )
70
85
return True
71
86
72
87
except Application .DoesNotExist :
88
+ log .debug ("Failed body authentication: Application %s do not exists" % client_id )
73
89
return False
74
90
91
+ def _load_application (self , client_id , request ):
92
+ """
93
+ If request.client was not set, load application instance for given client_id and store it
94
+ in request.client
95
+ """
96
+ try :
97
+ request .client = request .client or Application .objects .get (client_id = client_id )
98
+ except Application .DoesNotExist :
99
+ log .debug ("Application %s do not exists" % client_id )
100
+
101
+ def client_authentication_required (self , request , * args , ** kwargs ):
102
+ """
103
+ Determine if the client has to be authenticated
104
+
105
+ This method is called only for grant types that supports client authentication:
106
+ * Authorization code grant
107
+ * Resource owner password grant
108
+ * Refresh token grant
109
+
110
+ If the request contains authorization headers, always authenticate the client no matter
111
+ the grant type.
112
+
113
+ If the request does not contain authorization headers, proceed with authentication only if
114
+ the client is of type `Confidential`.
115
+
116
+ If something goes wrong, call oauthlib implementation of the method.
117
+ """
118
+ if self ._extract_basic_auth (request ):
119
+ return True
120
+
121
+ if request .client_id and request .client_secret :
122
+ return True
123
+
124
+ self ._load_application (request .client_id , request )
125
+ if request .client :
126
+ return request .client .client_type == Application .CLIENT_CONFIDENTIAL
127
+
128
+ return super (OAuth2Validator , self ).client_authentication_required (request ,
129
+ * args , ** kwargs )
130
+
75
131
def authenticate_client (self , request , * args , ** kwargs ):
76
132
"""
77
133
Check if client exists and it's authenticating itself as in rfc:`3.2.1`
78
134
79
- First we try to authenticate with HTTP Basic Auth, and that is the PREFERRED authentication method.
135
+ First we try to authenticate with HTTP Basic Auth, and that is the PREFERRED
136
+ authentication method.
80
137
Whether this fails we support including the client credentials in the request-body, but
81
138
this method is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize
82
139
the HTTP Basic authentication scheme. See rfc:`2.3.1` for more details
@@ -90,8 +147,9 @@ def authenticate_client(self, request, *args, **kwargs):
90
147
91
148
def authenticate_client_id (self , client_id , request , * args , ** kwargs ):
92
149
"""
93
- If we are here, the client did not authenticate itself as in rfc:`3.2.1` and we can proceed only if the client
94
- exists and it's not of type 'Confidential'. Also assign Application instance to request.client.
150
+ If we are here, the client did not authenticate itself as in rfc:`3.2.1` and we can
151
+ proceed only if the client exists and it's not of type 'Confidential'.
152
+ Also assign Application instance to request.client.
95
153
"""
96
154
97
155
try :
@@ -119,14 +177,11 @@ def invalidate_authorization_code(self, client_id, code, request, *args, **kwarg
119
177
120
178
def validate_client_id (self , client_id , request , * args , ** kwargs ):
121
179
"""
122
- Ensure an Application exists with given client_id. Also assign Application instance to request.client.
180
+ Ensure an Application exists with given client_id. If it exists, it's assigned to
181
+ request.client.
123
182
"""
124
- try :
125
- request .client = request .client or Application .objects .get (client_id = client_id )
126
- return True
127
-
128
- except Application .DoesNotExist :
129
- return False
183
+ self ._load_application (client_id , request )
184
+ return request .client is not None
130
185
131
186
def get_default_redirect_uri (self , client_id , request , * args , ** kwargs ):
132
187
return request .client .default_redirect_uri
@@ -139,7 +194,8 @@ def validate_bearer_token(self, token, scopes, request):
139
194
return False
140
195
141
196
try :
142
- access_token = AccessToken .objects .select_related ("application" , "user" ).get (token = token )
197
+ access_token = AccessToken .objects .select_related ("application" , "user" ).get (
198
+ token = token )
143
199
if access_token .is_valid (scopes ):
144
200
request .client = access_token .application
145
201
request .user = access_token .user
@@ -173,8 +229,8 @@ def validate_grant_type(self, client_id, grant_type, client, request, *args, **k
173
229
174
230
def validate_response_type (self , client_id , response_type , client , request , * args , ** kwargs ):
175
231
"""
176
- We currently do not support the Authorization Endpoint Response Types registry as in rfc:`8.4`, so validate
177
- the response_type only if it matches 'code' or 'token'
232
+ We currently do not support the Authorization Endpoint Response Types registry as in
233
+ rfc:`8.4`, so validate the response_type only if it matches 'code' or 'token'
178
234
"""
179
235
if response_type == 'code' :
180
236
return client .authorization_grant_type == Application .GRANT_AUTHORIZATION_CODE
@@ -196,14 +252,17 @@ def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwarg
196
252
return request .client .redirect_uri_allowed (redirect_uri )
197
253
198
254
def save_authorization_code (self , client_id , code , request , * args , ** kwargs ):
199
- expires = timezone .now () + timedelta (seconds = oauth2_settings .AUTHORIZATION_CODE_EXPIRE_SECONDS )
200
- g = Grant (application = request .client , user = request .user , code = code ['code' ], expires = expires ,
201
- redirect_uri = request .redirect_uri , scope = ' ' .join (request .scopes ))
255
+ expires = timezone .now () + timedelta (
256
+ seconds = oauth2_settings .AUTHORIZATION_CODE_EXPIRE_SECONDS )
257
+ g = Grant (application = request .client , user = request .user , code = code ['code' ],
258
+ expires = expires , redirect_uri = request .redirect_uri ,
259
+ scope = ' ' .join (request .scopes ))
202
260
g .save ()
203
261
204
262
def save_bearer_token (self , token , request , * args , ** kwargs ):
205
263
"""
206
- Save access and refresh token, If refresh token is issued, remove old refresh tokens as in rfc:`6`
264
+ Save access and refresh token, If refresh token is issued, remove old refresh tokens as
265
+ in rfc:`6`
207
266
"""
208
267
if request .refresh_token :
209
268
# remove used refresh token
@@ -247,7 +306,8 @@ def validate_user(self, username, password, client, request, *args, **kwargs):
247
306
return False
248
307
249
308
def get_original_scopes (self , refresh_token , request , * args , ** kwargs ):
250
- # TODO: since this method is invoked *after* validate_refresh_token, could we avoid this second query for RefreshToken?
309
+ # TODO: since this method is invoked *after* validate_refresh_token, could we avoid this
310
+ # second query for RefreshToken?
251
311
rt = RefreshToken .objects .get (token = refresh_token )
252
312
return rt .access_token .scope
253
313
0 commit comments