Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/parameters.py: 13%

120 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:22 +0000

1""" 

2oauthlib.oauth2.rfc6749.parameters 

3~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

4 

5This module contains methods related to `Section 4`_ of the OAuth 2 RFC. 

6 

7.. _`Section 4`: https://tools.ietf.org/html/rfc6749#section-4 

8""" 

9import json 

10import os 

11import time 

12import urllib.parse as urlparse 

13 

14from oauthlib.common import add_params_to_qs, add_params_to_uri 

15from oauthlib.signals import scope_changed 

16 

17from .errors import ( 

18 InsecureTransportError, MismatchingStateError, MissingCodeError, 

19 MissingTokenError, MissingTokenTypeError, raise_from_error, 

20) 

21from .tokens import OAuth2Token 

22from .utils import is_secure_transport, list_to_scope, scope_to_list 

23 

24 

25def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, 

26 scope=None, state=None, code_challenge=None, code_challenge_method='plain', **kwargs): 

27 """Prepare the authorization grant request URI. 

28 

29 The client constructs the request URI by adding the following 

30 parameters to the query component of the authorization endpoint URI 

31 using the ``application/x-www-form-urlencoded`` format as defined by 

32 [`W3C.REC-html401-19991224`_]: 

33 

34 :param uri: 

35 :param client_id: The client identifier as described in `Section 2.2`_. 

36 :param response_type: To indicate which OAuth 2 grant/flow is required, 

37 "code" and "token". 

38 :param redirect_uri: The client provided URI to redirect back to after 

39 authorization as described in `Section 3.1.2`_. 

40 :param scope: The scope of the access request as described by 

41 `Section 3.3`_. 

42 :param state: An opaque value used by the client to maintain 

43 state between the request and callback. The authorization 

44 server includes this value when redirecting the user-agent 

45 back to the client. The parameter SHOULD be used for 

46 preventing cross-site request forgery as described in 

47 `Section 10.12`_. 

48 :param code_challenge: PKCE parameter. A challenge derived from the  

49 code_verifier that is sent in the authorization  

50 request, to be verified against later. 

51 :param code_challenge_method: PKCE parameter. A method that was used to derive the  

52 code_challenge. Defaults to "plain" if not present in the request. 

53 :param kwargs: Extra arguments to embed in the grant/authorization URL. 

54 

55 An example of an authorization code grant authorization URL: 

56 

57 .. code-block:: http 

58 

59 GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz 

60 &code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6&code_challenge_method=S256 

61 &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 

62 Host: server.example.com 

63 

64 .. _`W3C.REC-html401-19991224`: https://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224 

65 .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2 

66 .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2 

67 .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3 

68 .. _`section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12 

69 """ 

70 if not is_secure_transport(uri): 

71 raise InsecureTransportError() 

72 

73 params = [(('response_type', response_type)), 

74 (('client_id', client_id))] 

75 

76 if redirect_uri: 

77 params.append(('redirect_uri', redirect_uri)) 

78 if scope: 

79 params.append(('scope', list_to_scope(scope))) 

80 if state: 

81 params.append(('state', state)) 

82 if code_challenge is not None: 

83 params.append(('code_challenge', code_challenge)) 

84 params.append(('code_challenge_method', code_challenge_method)) 

85 

86 for k in kwargs: 

87 if kwargs[k]: 

88 params.append((str(k), kwargs[k])) 

89 

90 return add_params_to_uri(uri, params) 

91 

92 

93def prepare_token_request(grant_type, body='', include_client_id=True, code_verifier=None, **kwargs): 

94 """Prepare the access token request. 

95 

96 The client makes a request to the token endpoint by adding the 

97 following parameters using the ``application/x-www-form-urlencoded`` 

98 format in the HTTP request entity-body: 

99 

100 :param grant_type: To indicate grant type being used, i.e. "password", 

101 "authorization_code" or "client_credentials". 

102 

103 :param body: Existing request body (URL encoded string) to embed parameters 

104 into. This may contain extra parameters. Default ''. 

105 

106 :param include_client_id: `True` (default) to send the `client_id` in the 

107 body of the upstream request. This is required 

108 if the client is not authenticating with the 

109 authorization server as described in 

110 `Section 3.2.1`_. 

111 :type include_client_id: Boolean 

112 

113 :param client_id: Unicode client identifier. Will only appear if 

114 `include_client_id` is True. * 

115 

116 :param client_secret: Unicode client secret. Will only appear if set to a 

117 value that is not `None`. Invoking this function with 

118 an empty string will send an empty `client_secret` 

119 value to the server. * 

120 

121 :param code: If using authorization_code grant, pass the previously 

122 obtained authorization code as the ``code`` argument. * 

123 

124 :param redirect_uri: If the "redirect_uri" parameter was included in the 

125 authorization request as described in 

126 `Section 4.1.1`_, and their values MUST be identical. * 

127 

128 :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the 

129 authorization request to the token request. 

130 

131 :param kwargs: Extra arguments to embed in the request body. 

132 

133 Parameters marked with a `*` above are not explicit arguments in the 

134 function signature, but are specially documented arguments for items 

135 appearing in the generic `**kwargs` keyworded input. 

136 

137 An example of an authorization code token request body: 

138 

139 .. code-block:: http 

140 

141 grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA 

142 &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 

143 

144 .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1 

145 """ 

146 params = [('grant_type', grant_type)] 

147 

148 if 'scope' in kwargs: 

149 kwargs['scope'] = list_to_scope(kwargs['scope']) 

150 

151 # pull the `client_id` out of the kwargs. 

152 client_id = kwargs.pop('client_id', None) 

153 if include_client_id: 

154 if client_id is not None: 

155 params.append(('client_id', client_id)) 

156 

157 # use code_verifier if code_challenge was passed in the authorization request 

158 if code_verifier is not None: 

159 params.append(('code_verifier', code_verifier)) 

160 

161 # the kwargs iteration below only supports including boolean truth (truthy) 

162 # values, but some servers may require an empty string for `client_secret` 

163 client_secret = kwargs.pop('client_secret', None) 

164 if client_secret is not None: 

165 params.append(('client_secret', client_secret)) 

166 

167 # this handles: `code`, `redirect_uri`, and other undocumented params 

168 for k in kwargs: 

169 if kwargs[k]: 

170 params.append((str(k), kwargs[k])) 

171 

172 return add_params_to_qs(body, params) 

173 

174 

175def prepare_token_revocation_request(url, token, token_type_hint="access_token", 

176 callback=None, body='', **kwargs): 

177 """Prepare a token revocation request. 

178 

179 The client constructs the request by including the following parameters 

180 using the ``application/x-www-form-urlencoded`` format in the HTTP request 

181 entity-body: 

182 

183 :param token: REQUIRED. The token that the client wants to get revoked. 

184 

185 :param token_type_hint: OPTIONAL. A hint about the type of the token 

186 submitted for revocation. Clients MAY pass this 

187 parameter in order to help the authorization server 

188 to optimize the token lookup. If the server is 

189 unable to locate the token using the given hint, it 

190 MUST extend its search across all of its supported 

191 token types. An authorization server MAY ignore 

192 this parameter, particularly if it is able to detect 

193 the token type automatically. 

194 

195 This specification defines two values for `token_type_hint`: 

196 

197 * access_token: An access token as defined in [RFC6749], 

198 `Section 1.4`_ 

199 

200 * refresh_token: A refresh token as defined in [RFC6749], 

201 `Section 1.5`_ 

202 

203 Specific implementations, profiles, and extensions of this 

204 specification MAY define other values for this parameter using the 

205 registry defined in `Section 4.1.2`_. 

206 

207 .. _`Section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4 

208 .. _`Section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5 

209 .. _`Section 4.1.2`: https://tools.ietf.org/html/rfc7009#section-4.1.2 

210 

211 """ 

212 if not is_secure_transport(url): 

213 raise InsecureTransportError() 

214 

215 params = [('token', token)] 

216 

217 if token_type_hint: 

218 params.append(('token_type_hint', token_type_hint)) 

219 

220 for k in kwargs: 

221 if kwargs[k]: 

222 params.append((str(k), kwargs[k])) 

223 

224 headers = {'Content-Type': 'application/x-www-form-urlencoded'} 

225 

226 if callback: 

227 params.append(('callback', callback)) 

228 return add_params_to_uri(url, params), headers, body 

229 else: 

230 return url, headers, add_params_to_qs(body, params) 

231 

232 

233def parse_authorization_code_response(uri, state=None): 

234 """Parse authorization grant response URI into a dict. 

235 

236 If the resource owner grants the access request, the authorization 

237 server issues an authorization code and delivers it to the client by 

238 adding the following parameters to the query component of the 

239 redirection URI using the ``application/x-www-form-urlencoded`` format: 

240 

241 **code** 

242 REQUIRED. The authorization code generated by the 

243 authorization server. The authorization code MUST expire 

244 shortly after it is issued to mitigate the risk of leaks. A 

245 maximum authorization code lifetime of 10 minutes is 

246 RECOMMENDED. The client MUST NOT use the authorization code 

247 more than once. If an authorization code is used more than 

248 once, the authorization server MUST deny the request and SHOULD 

249 revoke (when possible) all tokens previously issued based on 

250 that authorization code. The authorization code is bound to 

251 the client identifier and redirection URI. 

252 

253 **state** 

254 REQUIRED if the "state" parameter was present in the client 

255 authorization request. The exact value received from the 

256 client. 

257 

258 :param uri: The full redirect URL back to the client. 

259 :param state: The state parameter from the authorization request. 

260 

261 For example, the authorization server redirects the user-agent by 

262 sending the following HTTP response: 

263 

264 .. code-block:: http 

265 

266 HTTP/1.1 302 Found 

267 Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA 

268 &state=xyz 

269 

270 """ 

271 if not is_secure_transport(uri): 

272 raise InsecureTransportError() 

273 

274 query = urlparse.urlparse(uri).query 

275 params = dict(urlparse.parse_qsl(query)) 

276 

277 if state and params.get('state', None) != state: 

278 raise MismatchingStateError() 

279 

280 if 'error' in params: 

281 raise_from_error(params.get('error'), params) 

282 

283 if not 'code' in params: 

284 raise MissingCodeError("Missing code parameter in response.") 

285 

286 return params 

287 

288 

289def parse_implicit_response(uri, state=None, scope=None): 

290 """Parse the implicit token response URI into a dict. 

291 

292 If the resource owner grants the access request, the authorization 

293 server issues an access token and delivers it to the client by adding 

294 the following parameters to the fragment component of the redirection 

295 URI using the ``application/x-www-form-urlencoded`` format: 

296 

297 **access_token** 

298 REQUIRED. The access token issued by the authorization server. 

299 

300 **token_type** 

301 REQUIRED. The type of the token issued as described in 

302 Section 7.1. Value is case insensitive. 

303 

304 **expires_in** 

305 RECOMMENDED. The lifetime in seconds of the access token. For 

306 example, the value "3600" denotes that the access token will 

307 expire in one hour from the time the response was generated. 

308 If omitted, the authorization server SHOULD provide the 

309 expiration time via other means or document the default value. 

310 

311 **scope** 

312 OPTIONAL, if identical to the scope requested by the client, 

313 otherwise REQUIRED. The scope of the access token as described 

314 by Section 3.3. 

315 

316 **state** 

317 REQUIRED if the "state" parameter was present in the client 

318 authorization request. The exact value received from the 

319 client. 

320 

321 :param uri: 

322 :param state: 

323 :param scope: 

324 

325 Similar to the authorization code response, but with a full token provided 

326 in the URL fragment: 

327 

328 .. code-block:: http 

329 

330 HTTP/1.1 302 Found 

331 Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA 

332 &state=xyz&token_type=example&expires_in=3600 

333 """ 

334 if not is_secure_transport(uri): 

335 raise InsecureTransportError() 

336 

337 fragment = urlparse.urlparse(uri).fragment 

338 params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True)) 

339 

340 for key in ('expires_in',): 

341 if key in params: # cast things to int 

342 params[key] = int(params[key]) 

343 

344 if 'scope' in params: 

345 params['scope'] = scope_to_list(params['scope']) 

346 

347 if 'expires_in' in params: 

348 params['expires_at'] = time.time() + int(params['expires_in']) 

349 

350 if state and params.get('state', None) != state: 

351 raise ValueError("Mismatching or missing state in params.") 

352 

353 params = OAuth2Token(params, old_scope=scope) 

354 validate_token_parameters(params) 

355 return params 

356 

357 

358def parse_token_response(body, scope=None): 

359 """Parse the JSON token response body into a dict. 

360 

361 The authorization server issues an access token and optional refresh 

362 token, and constructs the response by adding the following parameters 

363 to the entity body of the HTTP response with a 200 (OK) status code: 

364 

365 access_token 

366 REQUIRED. The access token issued by the authorization server. 

367 token_type 

368 REQUIRED. The type of the token issued as described in 

369 `Section 7.1`_. Value is case insensitive. 

370 expires_in 

371 RECOMMENDED. The lifetime in seconds of the access token. For 

372 example, the value "3600" denotes that the access token will 

373 expire in one hour from the time the response was generated. 

374 If omitted, the authorization server SHOULD provide the 

375 expiration time via other means or document the default value. 

376 refresh_token 

377 OPTIONAL. The refresh token which can be used to obtain new 

378 access tokens using the same authorization grant as described 

379 in `Section 6`_. 

380 scope 

381 OPTIONAL, if identical to the scope requested by the client, 

382 otherwise REQUIRED. The scope of the access token as described 

383 by `Section 3.3`_. 

384 

385 The parameters are included in the entity body of the HTTP response 

386 using the "application/json" media type as defined by [`RFC4627`_]. The 

387 parameters are serialized into a JSON structure by adding each 

388 parameter at the highest structure level. Parameter names and string 

389 values are included as JSON strings. Numerical values are included 

390 as JSON numbers. The order of parameters does not matter and can 

391 vary. 

392 

393 :param body: The full json encoded response body. 

394 :param scope: The scope requested during authorization. 

395 

396 For example: 

397 

398 .. code-block:: http 

399 

400 HTTP/1.1 200 OK 

401 Content-Type: application/json 

402 Cache-Control: no-store 

403 Pragma: no-cache 

404 

405 { 

406 "access_token":"2YotnFZFEjr1zCsicMWpAA", 

407 "token_type":"example", 

408 "expires_in":3600, 

409 "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", 

410 "example_parameter":"example_value" 

411 } 

412 

413 .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1 

414 .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6 

415 .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3 

416 .. _`RFC4627`: https://tools.ietf.org/html/rfc4627 

417 """ 

418 try: 

419 params = json.loads(body) 

420 except ValueError: 

421 

422 # Fall back to URL-encoded string, to support old implementations, 

423 # including (at time of writing) Facebook. See: 

424 # https://github.com/oauthlib/oauthlib/issues/267 

425 

426 params = dict(urlparse.parse_qsl(body)) 

427 for key in ('expires_in',): 

428 if key in params: # cast things to int 

429 params[key] = int(params[key]) 

430 

431 if 'scope' in params: 

432 params['scope'] = scope_to_list(params['scope']) 

433 

434 if 'expires_in' in params: 

435 if params['expires_in'] is None: 

436 params.pop('expires_in') 

437 else: 

438 params['expires_at'] = time.time() + int(params['expires_in']) 

439 

440 params = OAuth2Token(params, old_scope=scope) 

441 validate_token_parameters(params) 

442 return params 

443 

444 

445def validate_token_parameters(params): 

446 """Ensures token presence, token type, expiration and scope in params.""" 

447 if 'error' in params: 

448 raise_from_error(params.get('error'), params) 

449 

450 if not 'access_token' in params: 

451 raise MissingTokenError(description="Missing access token parameter.") 

452 

453 if not 'token_type' in params: 

454 if os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'): 

455 raise MissingTokenTypeError() 

456 

457 # If the issued access token scope is different from the one requested by 

458 # the client, the authorization server MUST include the "scope" response 

459 # parameter to inform the client of the actual scope granted. 

460 # https://tools.ietf.org/html/rfc6749#section-3.3 

461 if params.scope_changed: 

462 message = 'Scope has changed from "{old}" to "{new}".'.format( 

463 old=params.old_scope, new=params.scope, 

464 ) 

465 scope_changed.send(message=message, old=params.old_scopes, new=params.scopes) 

466 if not os.environ.get('OAUTHLIB_RELAX_TOKEN_SCOPE', None): 

467 w = Warning(message) 

468 w.token = params 

469 w.old_scope = params.old_scopes 

470 w.new_scope = params.scopes 

471 raise w