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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

159 statements  

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 and client_id is not None: 

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

155 

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

157 if code_verifier is not None: 

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

159 

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

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

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

163 if client_secret is not None: 

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

165 

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

167 for k in kwargs: 

168 if kwargs[k]: 

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

170 

171 return add_params_to_qs(body, params) 

172 

173 

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

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

176 """Prepare a token revocation request. 

177 

178 The client constructs the request by including the following parameters 

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

180 entity-body: 

181 

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

183 

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

185 submitted for revocation. Clients MAY pass this 

186 parameter in order to help the authorization server 

187 to optimize the token lookup. If the server is 

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

189 MUST extend its search across all of its supported 

190 token types. An authorization server MAY ignore 

191 this parameter, particularly if it is able to detect 

192 the token type automatically. 

193 

194 This specification defines two values for `token_type_hint`: 

195 

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

197 `Section 1.4`_ 

198 

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

200 `Section 1.5`_ 

201 

202 Specific implementations, profiles, and extensions of this 

203 specification MAY define other values for this parameter using the 

204 registry defined in `Section 4.1.2`_. 

205 

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

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

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

209 

210 """ 

211 if not is_secure_transport(url): 

212 raise InsecureTransportError() 

213 

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

215 

216 if token_type_hint: 

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

218 

219 for k in kwargs: 

220 if kwargs[k]: 

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

222 

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

224 

225 if callback: 

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

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

228 else: 

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

230 

231 

232def parse_authorization_code_response(uri, state=None): 

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

234 

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

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

237 adding the following parameters to the query component of the 

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

239 

240 **code** 

241 REQUIRED. The authorization code generated by the 

242 authorization server. The authorization code MUST expire 

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

244 maximum authorization code lifetime of 10 minutes is 

245 RECOMMENDED. The client MUST NOT use the authorization code 

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

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

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

249 that authorization code. The authorization code is bound to 

250 the client identifier and redirection URI. 

251 

252 **state** 

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

254 authorization request. The exact value received from the 

255 client. 

256 

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

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

259 

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

261 sending the following HTTP response: 

262 

263 .. code-block:: http 

264 

265 HTTP/1.1 302 Found 

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

267 &state=xyz 

268 

269 """ 

270 if not is_secure_transport(uri): 

271 raise InsecureTransportError() 

272 

273 query = urlparse.urlparse(uri).query 

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

275 

276 if state and params.get('state') != state: 

277 raise MismatchingStateError() 

278 

279 if 'error' in params: 

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

281 

282 if 'code' not in params: 

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

284 

285 return params 

286 

287 

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

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

290 

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

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

293 the following parameters to the fragment component of the redirection 

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

295 

296 **access_token** 

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

298 

299 **token_type** 

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

301 Section 7.1. Value is case insensitive. 

302 

303 **expires_in** 

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

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

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

307 If omitted, the authorization server SHOULD provide the 

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

309 

310 **scope** 

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

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

313 by Section 3.3. 

314 

315 **state** 

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

317 authorization request. The exact value received from the 

318 client. 

319 

320 :param uri: 

321 :param state: 

322 :param scope: 

323 

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

325 in the URL fragment: 

326 

327 .. code-block:: http 

328 

329 HTTP/1.1 302 Found 

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

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

332 """ 

333 if not is_secure_transport(uri): 

334 raise InsecureTransportError() 

335 

336 fragment = urlparse.urlparse(uri).fragment 

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

338 

339 if 'scope' in params: 

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

341 

342 vin, vat, v_at = parse_expires(params) 

343 if vin: 

344 params['expires_in'] = vin 

345 elif 'expires_in' in params: 

346 params.pop('expires_in') 

347 if vat: 

348 params['expires_at'] = vat 

349 elif 'expires_at' in params: 

350 params.pop('expires_at') 

351 

352 if state and params.get('state') != state: 

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

354 

355 params = OAuth2Token(params, old_scope=scope) 

356 validate_token_parameters(params) 

357 return params 

358 

359 

360def parse_token_response(body, scope=None): 

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

362 

363 The authorization server issues an access token and optional refresh 

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

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

366 

367 access_token 

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

369 token_type 

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

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

372 expires_in 

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

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

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

376 If omitted, the authorization server SHOULD provide the 

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

378 refresh_token 

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

380 access tokens using the same authorization grant as described 

381 in `Section 6`_. 

382 scope 

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

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

385 by `Section 3.3`_. 

386 

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

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

389 parameters are serialized into a JSON structure by adding each 

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

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

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

393 vary. 

394 

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

396 :param scope: The scope requested during authorization. 

397 

398 For example: 

399 

400 .. code-block:: http 

401 

402 HTTP/1.1 200 OK 

403 Content-Type: application/json 

404 Cache-Control: no-store 

405 Pragma: no-cache 

406 

407 { 

408 "access_token":"2YotnFZFEjr1zCsicMWpAA", 

409 "token_type":"example", 

410 "expires_in":3600, 

411 "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", 

412 "example_parameter":"example_value" 

413 } 

414 

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

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

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

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

419 """ 

420 try: 

421 params = json.loads(body) 

422 except ValueError: 

423 

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

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

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

427 

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

429 

430 if 'scope' in params: 

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

432 

433 vin, vat, v_at = parse_expires(params) 

434 if vin: 

435 params['expires_in'] = vin 

436 elif 'expires_in' in params: 

437 params.pop('expires_in') 

438 if vat: 

439 params['expires_at'] = vat 

440 elif 'expires_at' in params: 

441 params.pop('expires_at') 

442 

443 params = OAuth2Token(params, old_scope=scope) 

444 validate_token_parameters(params) 

445 return params 

446 

447 

448def validate_token_parameters(params): 

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

450 if 'error' in params: 

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

452 

453 if 'access_token' not in params: 

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

455 

456 if 'token_type' not in params and os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'): 

457 raise MissingTokenTypeError() 

458 

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

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

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

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

463 if params.scope_changed: 

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

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

466 ) 

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

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

469 w = Warning(message) 

470 w.token = params 

471 w.old_scope = params.old_scopes 

472 w.new_scope = params.scopes 

473 raise w 

474 

475def parse_expires(params): 

476 """Parse `expires_in`, `expires_at` fields from params 

477 

478 Parse following these rules: 

479 - `expires_in` must be either integer, float or None. If a float, it is converted into an integer. 

480 - `expires_at` is not in specification so it does its best to: 

481 - convert into a int, else 

482 - convert into a float, else 

483 - reuse the same type as-is (usually string) 

484 - `_expires_at` is a special internal value returned to be always an `int`, based 

485 either on the presence of `expires_at`, or reuse the current time plus 

486 `expires_in`. This is typically used to validate token expiry. 

487 

488 :param params: Dict with expires_in and expires_at optionally set 

489 :return: Tuple of `expires_in`, `expires_at`, and `_expires_at`. None if not set. 

490 """ 

491 expires_in = None 

492 expires_at = None 

493 _expires_at = None 

494 

495 if 'expires_in' in params: 

496 if isinstance(params.get('expires_in'), int): 

497 expires_in = params.get('expires_in') 

498 elif isinstance(params.get('expires_in'), float): 

499 expires_in = int(params.get('expires_in')) 

500 elif isinstance(params.get('expires_in'), str): 

501 try: 

502 # Attempt to convert to int 

503 expires_in = int(params.get('expires_in')) 

504 except ValueError: 

505 raise ValueError("expires_in must be an int") 

506 elif params.get('expires_in') is not None: 

507 raise ValueError("expires_in must be an int") 

508 

509 if 'expires_at' in params: 

510 if isinstance(params.get('expires_at'), (float, int)): 

511 expires_at = params.get('expires_at') 

512 _expires_at = expires_at 

513 elif isinstance(params.get('expires_at'), str): 

514 try: 

515 # Attempt to convert to int first, then float if int fails 

516 expires_at = int(params.get('expires_at')) 

517 _expires_at = expires_at 

518 except ValueError: 

519 try: 

520 expires_at = float(params.get('expires_at')) 

521 _expires_at = expires_at 

522 except ValueError: 

523 # no change from str 

524 expires_at = params.get('expires_at') 

525 if _expires_at is None and expires_in: 

526 expires_at = round(time.time()) + expires_in 

527 _expires_at = expires_at 

528 return expires_in, expires_at, _expires_at