1"""
2oauthlib.oauth2.rfc6749.grant_types
3~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4"""
5import json
6import logging
7
8from .. import errors
9from .base import GrantTypeBase
10
11log = logging.getLogger(__name__)
12
13
14class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
15
16 """`Resource Owner Password Credentials Grant`_
17
18 The resource owner password credentials grant type is suitable in
19 cases where the resource owner has a trust relationship with the
20 client, such as the device operating system or a highly privileged
21 application. The authorization server should take special care when
22 enabling this grant type and only allow it when other flows are not
23 viable.
24
25 This grant type is suitable for clients capable of obtaining the
26 resource owner's credentials (username and password, typically using
27 an interactive form). It is also used to migrate existing clients
28 using direct authentication schemes such as HTTP Basic or Digest
29 authentication to OAuth by converting the stored credentials to an
30 access token::
31
32 +----------+
33 | Resource |
34 | Owner |
35 | |
36 +----------+
37 v
38 | Resource Owner
39 (A) Password Credentials
40 |
41 v
42 +---------+ +---------------+
43 | |>--(B)---- Resource Owner ------->| |
44 | | Password Credentials | Authorization |
45 | Client | | Server |
46 | |<--(C)---- Access Token ---------<| |
47 | | (w/ Optional Refresh Token) | |
48 +---------+ +---------------+
49
50 Figure 5: Resource Owner Password Credentials Flow
51
52 The flow illustrated in Figure 5 includes the following steps:
53
54 (A) The resource owner provides the client with its username and
55 password.
56
57 (B) The client requests an access token from the authorization
58 server's token endpoint by including the credentials received
59 from the resource owner. When making the request, the client
60 authenticates with the authorization server.
61
62 (C) The authorization server authenticates the client and validates
63 the resource owner credentials, and if valid, issues an access
64 token.
65
66 .. _`Resource Owner Password Credentials Grant`: https://tools.ietf.org/html/rfc6749#section-4.3
67 """
68
69 def create_token_response(self, request, token_handler):
70 """Return token or error in json format.
71
72 :param request: OAuthlib request.
73 :type request: oauthlib.common.Request
74 :param token_handler: A token handler instance, for example of type
75 oauthlib.oauth2.BearerToken.
76
77 If the access token request is valid and authorized, the
78 authorization server issues an access token and optional refresh
79 token as described in `Section 5.1`_. If the request failed client
80 authentication or is invalid, the authorization server returns an
81 error response as described in `Section 5.2`_.
82
83 .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
84 .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
85 """
86 headers = self._get_default_headers()
87 try:
88 if self.request_validator.client_authentication_required(request):
89 log.debug('Authenticating client, %r.', request)
90 if not self.request_validator.authenticate_client(request):
91 log.debug('Client authentication failed, %r.', request)
92 raise errors.InvalidClientError(request=request)
93 elif not self.request_validator.authenticate_client_id(request.client_id, request):
94 log.debug('Client authentication failed, %r.', request)
95 raise errors.InvalidClientError(request=request)
96 log.debug('Validating access token request, %r.', request)
97 self.validate_token_request(request)
98 except errors.OAuth2Error as e:
99 log.debug('Client error in token request, %s.', e)
100 headers.update(e.headers)
101 return headers, e.json, e.status_code
102
103 token = token_handler.create_token(request, self.refresh_token)
104
105 for modifier in self._token_modifiers:
106 token = modifier(token)
107
108 self.request_validator.save_token(token, request)
109
110 log.debug('Issuing token %r to client id %r (%r) and username %s.',
111 token, request.client_id, request.client, request.username)
112 return headers, json.dumps(token), 200
113
114 def validate_token_request(self, request):
115 """
116 :param request: OAuthlib request.
117 :type request: oauthlib.common.Request
118
119 The client makes a request to the token endpoint by adding the
120 following parameters using the "application/x-www-form-urlencoded"
121 format per Appendix B with a character encoding of UTF-8 in the HTTP
122 request entity-body:
123
124 grant_type
125 REQUIRED. Value MUST be set to "password".
126
127 username
128 REQUIRED. The resource owner username.
129
130 password
131 REQUIRED. The resource owner password.
132
133 scope
134 OPTIONAL. The scope of the access request as described by
135 `Section 3.3`_.
136
137 If the client type is confidential or the client was issued client
138 credentials (or assigned other authentication requirements), the
139 client MUST authenticate with the authorization server as described
140 in `Section 3.2.1`_.
141
142 The authorization server MUST:
143
144 o require client authentication for confidential clients or for any
145 client that was issued client credentials (or with other
146 authentication requirements),
147
148 o authenticate the client if client authentication is included, and
149
150 o validate the resource owner password credentials using its
151 existing password validation algorithm.
152
153 Since this access token request utilizes the resource owner's
154 password, the authorization server MUST protect the endpoint against
155 brute force attacks (e.g., using rate-limitation or generating
156 alerts).
157
158 .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
159 .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
160 """
161 for validator in self.custom_validators.pre_token:
162 validator(request)
163
164 for param in ('grant_type', 'username', 'password'):
165 if not getattr(request, param, None):
166 raise errors.InvalidRequestError(
167 'Request is missing %s parameter.' % param, request=request)
168
169 for param in ('grant_type', 'username', 'password', 'scope'):
170 if param in request.duplicate_params:
171 raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param, request=request)
172
173 # This error should rarely (if ever) occur if requests are routed to
174 # grant type handlers based on the grant_type parameter.
175 if not request.grant_type == 'password':
176 raise errors.UnsupportedGrantTypeError(request=request)
177
178 log.debug('Validating username %s.', request.username)
179 if not self.request_validator.validate_user(request.username,
180 request.password, request.client, request):
181 raise errors.InvalidGrantError(
182 'Invalid credentials given.', request=request)
183 elif not hasattr(request.client, 'client_id'):
184 raise NotImplementedError(
185 'Validate user must set the '
186 'request.client.client_id attribute '
187 'in authenticate_client.')
188 log.debug('Authorizing access to user %r.', request.user)
189
190 # Ensure client is authorized use of this grant type
191 self.validate_grant_type(request)
192
193 if request.client:
194 request.client_id = request.client_id or request.client.client_id
195 self.validate_scopes(request)
196
197 for validator in self.custom_validators.post_token:
198 validator(request)