1 # -*- coding: utf-8 -*-
3 oauthlib.oauth2.rfc6749.grant_types
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 from __future__ import unicode_literals, absolute_import
11 from .base import GrantTypeBase
13 from ..request_validator import RequestValidator
15 log = logging.getLogger(__name__)
18 class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
20 """`Resource Owner Password Credentials Grant`_
22 The resource owner password credentials grant type is suitable in
23 cases where the resource owner has a trust relationship with the
24 client, such as the device operating system or a highly privileged
25 application. The authorization server should take special care when
26 enabling this grant type and only allow it when other flows are not
29 This grant type is suitable for clients capable of obtaining the
30 resource owner's credentials (username and password, typically using
31 an interactive form). It is also used to migrate existing clients
32 using direct authentication schemes such as HTTP Basic or Digest
33 authentication to OAuth by converting the stored credentials to an
43 (A) Password Credentials
46 +---------+ +---------------+
47 | |>--(B)---- Resource Owner ------->| |
48 | | Password Credentials | Authorization |
50 | |<--(C)---- Access Token ---------<| |
51 | | (w/ Optional Refresh Token) | |
52 +---------+ +---------------+
54 Figure 5: Resource Owner Password Credentials Flow
56 The flow illustrated in Figure 5 includes the following steps:
58 (A) The resource owner provides the client with its username and
61 (B) The client requests an access token from the authorization
62 server's token endpoint by including the credentials received
63 from the resource owner. When making the request, the client
64 authenticates with the authorization server.
66 (C) The authorization server authenticates the client and validates
67 the resource owner credentials, and if valid, issues an access
70 .. _`Resource Owner Password Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.3
73 def __init__(self, request_validator=None, refresh_token=True):
75 If the refresh_token keyword argument is False, do not return
76 a refresh token in the response.
78 self.request_validator = request_validator or RequestValidator()
79 self.refresh_token = refresh_token
81 def create_token_response(self, request, token_handler):
82 """Return token or error in json format.
84 If the access token request is valid and authorized, the
85 authorization server issues an access token and optional refresh
86 token as described in `Section 5.1`_. If the request failed client
87 authentication or is invalid, the authorization server returns an
88 error response as described in `Section 5.2`_.
90 .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
91 .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
94 'Content-Type': 'application/json',
95 'Cache-Control': 'no-store',
99 if self.request_validator.client_authentication_required(request):
100 log.debug('Authenticating client, %r.', request)
101 if not self.request_validator.authenticate_client(request):
102 log.debug('Client authentication failed, %r.', request)
103 raise errors.InvalidClientError(request=request)
104 elif not self.request_validator.authenticate_client_id(request.client_id, request):
105 log.debug('Client authentication failed, %r.', request)
106 raise errors.InvalidClientError(request=request)
107 log.debug('Validating access token request, %r.', request)
108 self.validate_token_request(request)
109 except errors.OAuth2Error as e:
110 log.debug('Client error in token request, %s.', e)
111 return headers, e.json, e.status_code
113 token = token_handler.create_token(request, self.refresh_token)
114 log.debug('Issuing token %r to client id %r (%r) and username %s.',
115 token, request.client_id, request.client, request.username)
116 return headers, json.dumps(token), 200
118 def validate_token_request(self, request):
120 The client makes a request to the token endpoint by adding the
121 following parameters using the "application/x-www-form-urlencoded"
122 format per Appendix B with a character encoding of UTF-8 in the HTTP
126 REQUIRED. Value MUST be set to "password".
129 REQUIRED. The resource owner username.
132 REQUIRED. The resource owner password.
135 OPTIONAL. The scope of the access request as described by
138 If the client type is confidential or the client was issued client
139 credentials (or assigned other authentication requirements), the
140 client MUST authenticate with the authorization server as described
143 The authorization server MUST:
145 o require client authentication for confidential clients or for any
146 client that was issued client credentials (or with other
147 authentication requirements),
149 o authenticate the client if client authentication is included, and
151 o validate the resource owner password credentials using its
152 existing password validation algorithm.
154 Since this access token request utilizes the resource owner's
155 password, the authorization server MUST protect the endpoint against
156 brute force attacks (e.g., using rate-limitation or generating
159 .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
160 .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
162 for param in ('grant_type', 'username', 'password'):
163 if not getattr(request, param):
164 raise errors.InvalidRequestError(
165 'Request is missing %s parameter.' % param, request=request)
167 for param in ('grant_type', 'username', 'password', 'scope'):
168 if param in request.duplicate_params:
169 raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param, request=request)
171 # This error should rarely (if ever) occur if requests are routed to
172 # grant type handlers based on the grant_type parameter.
173 if not request.grant_type == 'password':
174 raise errors.UnsupportedGrantTypeError(request=request)
176 log.debug('Validating username %s.', request.username)
177 if not self.request_validator.validate_user(request.username,
178 request.password, request.client, request):
179 raise errors.InvalidGrantError(
180 'Invalid credentials given.', request=request)
182 if not hasattr(request.client, 'client_id'):
183 raise NotImplementedError(
184 'Validate user must set the '
185 'request.client.client_id attribute '
186 'in authenticate_client.')
187 log.debug('Authorizing access to user %r.', request.user)
189 # Ensure client is authorized use of this grant type
190 self.validate_grant_type(request)
193 request.client_id = request.client_id or request.client.client_id
194 self.validate_scopes(request)