From 8b33f83fdcd5aed1d319534161dcb4bca73e28ed Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 24 Aug 2023 17:45:46 +0200 Subject: [PATCH] Lint with ruff to replace bandit, flake8, isort, pyupgrade (#855) * Lint with ruff to replace bandit, flake8, isort, pyupgrade * Readability fixes --- .github/workflows/lint_python.yml | 11 +- docs/conf.py | 2 +- examples/__init__.py | 0 oauthlib/__init__.py | 20 ++-- oauthlib/common.py | 5 +- oauthlib/oauth1/rfc5849/__init__.py | 5 +- oauthlib/oauth1/rfc5849/endpoints/base.py | 11 +- oauthlib/oauth1/rfc5849/signature.py | 6 +- oauthlib/oauth1/rfc5849/utils.py | 5 +- oauthlib/oauth2/rfc6749/clients/base.py | 8 +- .../rfc6749/clients/mobile_application.py | 2 +- .../oauth2/rfc6749/clients/web_application.py | 6 +- oauthlib/oauth2/rfc6749/endpoints/base.py | 4 +- .../oauth2/rfc6749/endpoints/introspect.py | 2 +- oauthlib/oauth2/rfc6749/endpoints/metadata.py | 4 +- oauthlib/oauth2/rfc6749/errors.py | 1 - .../rfc6749/grant_types/authorization_code.py | 7 +- oauthlib/oauth2/rfc6749/grant_types/base.py | 7 +- .../rfc6749/grant_types/client_credentials.py | 9 +- .../oauth2/rfc6749/grant_types/implicit.py | 5 +- .../resource_owner_password_credentials.py | 11 +- oauthlib/oauth2/rfc6749/parameters.py | 20 ++-- oauthlib/oauth2/rfc6749/request_validator.py | 4 +- oauthlib/oauth2/rfc6749/tokens.py | 10 +- oauthlib/openid/connect/core/exceptions.py | 4 +- .../openid/connect/core/grant_types/hybrid.py | 11 +- oauthlib/openid/connect/core/tokens.py | 5 +- oauthlib/signals.py | 5 +- oauthlib/uri_validate.py | 3 +- ruff.toml | 107 ++++++++++++++++++ setup.py | 7 +- tests/oauth1/rfc5849/endpoints/test_base.py | 2 +- tests/oauth1/rfc5849/test_signatures.py | 59 +++++----- .../oauth2/rfc6749/endpoints/test_metadata.py | 4 +- tests/oauth2/rfc6749/test_utils.py | 2 +- 35 files changed, 227 insertions(+), 147 deletions(-) create mode 100644 examples/__init__.py create mode 100644 ruff.toml diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index 7cb2b28e..f50e8aa9 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -8,20 +8,15 @@ jobs: - uses: actions/setup-python@v4 with: python-version: 3.x + check-latest: true - run: pip install --upgrade pip setuptools wheel - - run: pip install bandit black codespell flake8 flake8-2020 flake8-bugbear - flake8-comprehensions isort mypy pytest pyupgrade safety - - run: bandit --recursive --skip B101,B105,B106,B107,B324 . + - run: pip install black codespell mypy pytest ruff safety + - run: ruff --format=github . - run: black --check . || true - run: codespell # --ignore-words-list="" --skip="*.css,*.js,*.lock" - - run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - - run: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 - --show-source --statistics - - run: isort --check-only --profile black . || true - run: pip install -r requirements-test.txt - run: pip install --editable . - run: mkdir --parents --verbose .mypy_cache - run: mypy --ignore-missing-imports --install-types --non-interactive . || true - run: pytest - - run: shopt -s globstar && pyupgrade --py37-plus **/*.py || true - run: safety check diff --git a/docs/conf.py b/docs/conf.py index f4b92c47..05e93ee0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,7 +54,7 @@ # # The short X.Y version. -from oauthlib import __version__ as v +from oauthlib import __version__ as v # noqa: E402 version = v[:3] # The full version, including alpha/beta/rc tags. release = v diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index d9a5e38e..4f56ef14 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -19,16 +19,16 @@ _DEBUG = False def set_debug(debug_val): - """Set value of debug flag - + """Set value of debug flag + :param debug_val: Value to set. Must be a bool value. - """ - global _DEBUG - _DEBUG = debug_val + """ + global _DEBUG # noqa: PLW0603 + _DEBUG = debug_val def get_debug(): - """Get debug mode value. - - :return: `True` if debug mode is on, `False` otherwise - """ - return _DEBUG + """Get debug mode value. + + :return: `True` if debug mode is on, `False` otherwise + """ + return _DEBUG diff --git a/oauthlib/common.py b/oauthlib/common.py index 395e75ef..9d850b9a 100644 --- a/oauthlib/common.py +++ b/oauthlib/common.py @@ -34,7 +34,7 @@ always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' - '0123456789' '_.-') + '0123456789_.-') log = logging.getLogger('oauthlib') @@ -346,7 +346,8 @@ class Request: def __init__(self, uri, http_method='GET', body=None, headers=None, encoding='utf-8'): # Convert to unicode using encoding if given, else assume unicode - encode = lambda x: to_unicode(x, encoding) if encoding else x + def encode(x): + return to_unicode(x, encoding) if encoding else x self.uri = encode(uri) self.http_method = encode(http_method) diff --git a/oauthlib/oauth1/rfc5849/__init__.py b/oauthlib/oauth1/rfc5849/__init__.py index c559251f..85e0b90b 100644 --- a/oauthlib/oauth1/rfc5849/__init__.py +++ b/oauthlib/oauth1/rfc5849/__init__.py @@ -121,7 +121,8 @@ def __init__(self, client_key, :param timestamp: Use this timestamp instead of using current. (Mainly for testing) """ # Convert to unicode using encoding if given, else assume unicode - encode = lambda x: to_unicode(x, encoding) if encoding else x + def encode(x): + return to_unicode(x, encoding) if encoding else x self.client_key = encode(client_key) self.client_secret = encode(client_secret) @@ -219,7 +220,7 @@ def get_oauth_params(self, request): content_type = request.headers.get('Content-Type', None) content_type_eligible = content_type and content_type.find('application/x-www-form-urlencoded') < 0 if request.body is not None and content_type_eligible: - params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8'))) + params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8'))) # noqa: S324 return params diff --git a/oauthlib/oauth1/rfc5849/endpoints/base.py b/oauthlib/oauth1/rfc5849/endpoints/base.py index 7831be7c..8d3d89c6 100644 --- a/oauthlib/oauth1/rfc5849/endpoints/base.py +++ b/oauthlib/oauth1/rfc5849/endpoints/base.py @@ -69,12 +69,10 @@ def _get_signature_type_and_params(self, request): def _create_request(self, uri, http_method, body, headers): # Only include body data from x-www-form-urlencoded requests headers = CaseInsensitiveDict(headers or {}) - if ("Content-Type" in headers and - CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]): + if "Content-Type" in headers and CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]: # noqa: SIM108 request = Request(uri, http_method, body, headers) else: request = Request(uri, http_method, '', headers) - signature_type, params, oauth_params = ( self._get_signature_type_and_params(request)) @@ -129,8 +127,7 @@ def _check_mandatory_parameters(self, request): # Considerations section (`Section 4`_) before deciding on which # method to support. # .. _`Section 4`: https://tools.ietf.org/html/rfc5849#section-4 - if (not request.signature_method in - self.request_validator.allowed_signature_methods): + if (request.signature_method not in self.request_validator.allowed_signature_methods): raise errors.InvalidSignatureMethodError( description="Invalid signature, {} not in {!r}.".format( request.signature_method, @@ -180,9 +177,7 @@ def _check_mandatory_parameters(self, request): def _check_signature(self, request, is_token_request=False): # ---- RSA Signature verification ---- - if request.signature_method == SIGNATURE_RSA_SHA1 or \ - request.signature_method == SIGNATURE_RSA_SHA256 or \ - request.signature_method == SIGNATURE_RSA_SHA512: + if request.signature_method in {SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512}: # RSA-based signature method # The server verifies the signature per `[RFC3447] section 8.2.2`_ diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py index 9cb1a517..8916782b 100644 --- a/oauthlib/oauth1/rfc5849/signature.py +++ b/oauthlib/oauth1/rfc5849/signature.py @@ -45,6 +45,7 @@ from oauthlib.common import extract_params, safe_string_equals, urldecode from . import utils +import contextlib log = logging.getLogger(__name__) @@ -188,10 +189,9 @@ def base_string_uri(uri: str, host: str = None) -> str: raise ValueError('missing host') # NOTE: Try guessing if we're dealing with IP or hostname - try: + with contextlib.suppress(ValueError): hostname = ipaddress.ip_address(hostname) - except ValueError: - pass + if isinstance(hostname, ipaddress.IPv6Address): hostname = f"[{hostname}]" diff --git a/oauthlib/oauth1/rfc5849/utils.py b/oauthlib/oauth1/rfc5849/utils.py index 8fb8302e..0915105b 100644 --- a/oauthlib/oauth1/rfc5849/utils.py +++ b/oauthlib/oauth1/rfc5849/utils.py @@ -30,7 +30,8 @@ def wrapper(params, *args, **kwargs): def filter_oauth_params(params): """Removes all non oauth parameters from a dict or a list of params.""" - is_oauth = lambda kv: kv[0].startswith("oauth_") + def is_oauth(kv): + return kv[0].startswith('oauth_') if isinstance(params, dict): return list(filter(is_oauth, list(params.items()))) else: @@ -59,7 +60,7 @@ def unescape(u): return unquote(u) -def parse_keqv_list(l): +def parse_keqv_list(l): # noqa: E741 """A unicode-safe version of urllib2.parse_keqv_list""" # With Python 2.6, parse_http_list handles unicode fine return urllib2.parse_keqv_list(l) diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py index 1d12638e..cdcfa55a 100644 --- a/oauthlib/oauth2/rfc6749/clients/base.py +++ b/oauthlib/oauth2/rfc6749/clients/base.py @@ -207,7 +207,7 @@ def add_token(self, uri, http_method='GET', body=None, headers=None, case_insensitive_token_types = { k.lower(): v for k, v in self.token_types.items()} - if not self.token_type.lower() in case_insensitive_token_types: + if self.token_type.lower() not in case_insensitive_token_types: raise ValueError("Unsupported token type: %s" % self.token_type) if not (self.access_token or self.token.get('access_token')): @@ -466,7 +466,7 @@ def _add_bearer_token(self, uri, http_method='GET', body=None, return uri, headers, body def create_code_verifier(self, length): - """Create PKCE **code_verifier** used in computing **code_challenge**. + """Create PKCE **code_verifier** used in computing **code_challenge**. See `RFC7636 Section 4.1`_ :param length: REQUIRED. The length of the code_verifier. @@ -530,10 +530,10 @@ def create_code_challenge(self, code_verifier, code_challenge_method=None): """ code_challenge = None - if code_verifier == None: + if code_verifier is None: raise ValueError("Invalid code_verifier") - if code_challenge_method == None: + if code_challenge_method is None: code_challenge_method = "plain" self.code_challenge_method = code_challenge_method code_challenge = code_verifier diff --git a/oauthlib/oauth2/rfc6749/clients/mobile_application.py b/oauthlib/oauth2/rfc6749/clients/mobile_application.py index b10b41ce..023cf236 100644 --- a/oauthlib/oauth2/rfc6749/clients/mobile_application.py +++ b/oauthlib/oauth2/rfc6749/clients/mobile_application.py @@ -43,7 +43,7 @@ class MobileApplicationClient(Client): redirection URI, it may be exposed to the resource owner and other applications residing on the same device. """ - + response_type = 'token' def prepare_request_uri(self, uri, redirect_uri=None, scope=None, diff --git a/oauthlib/oauth2/rfc6749/clients/web_application.py b/oauthlib/oauth2/rfc6749/clients/web_application.py index 50890fbf..3bf94c4b 100644 --- a/oauthlib/oauth2/rfc6749/clients/web_application.py +++ b/oauthlib/oauth2/rfc6749/clients/web_application.py @@ -33,7 +33,7 @@ class WebApplicationClient(Client): browser) and capable of receiving incoming requests (via redirection) from the authorization server. """ - + grant_type = 'authorization_code' def __init__(self, client_id, code=None, **kwargs): @@ -62,8 +62,8 @@ def prepare_request_uri(self, uri, redirect_uri=None, scope=None, to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in `Section 10.12`_. - :param code_challenge: OPTIONAL. PKCE parameter. REQUIRED if PKCE is enforced. - A challenge derived from the code_verifier that is sent in the + :param code_challenge: OPTIONAL. PKCE parameter. REQUIRED if PKCE is enforced. + A challenge derived from the code_verifier that is sent in the authorization request, to be verified against later. :param code_challenge_method: OPTIONAL. PKCE parameter. A method that was used to derive code challenge. diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py index 3f239917..987fac6a 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -32,7 +32,7 @@ def valid_request_methods(self, valid_request_methods): if valid_request_methods is not None: valid_request_methods = [x.upper() for x in valid_request_methods] self._valid_request_methods = valid_request_methods - + @property def available(self): @@ -40,7 +40,7 @@ def available(self): @available.setter def available(self, available): - self._available = available + self._available = available @property def catch_errors(self): diff --git a/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/oauthlib/oauth2/rfc6749/endpoints/introspect.py index 3cc61e66..ef73988d 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/introspect.py +++ b/oauthlib/oauth2/rfc6749/endpoints/introspect.py @@ -74,7 +74,7 @@ def create_introspect_response(self, uri, http_method='POST', body=None, request ) if claims is None: - return resp_headers, json.dumps(dict(active=False)), 200 + return resp_headers, json.dumps({'active': False}), 200 if "active" in claims: claims.pop("active") return resp_headers, json.dumps(dict(active=True, **claims)), 200 diff --git a/oauthlib/oauth2/rfc6749/endpoints/metadata.py b/oauthlib/oauth2/rfc6749/endpoints/metadata.py index a2820f28..34274cba 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/metadata.py +++ b/oauthlib/oauth2/rfc6749/endpoints/metadata.py @@ -38,9 +38,9 @@ class MetadataEndpoint(BaseEndpoint): """ def __init__(self, endpoints, claims={}, raise_errors=True): - assert isinstance(claims, dict) + assert isinstance(claims, dict) # noqa: S101 for endpoint in endpoints: - assert isinstance(endpoint, BaseEndpoint) + assert isinstance(endpoint, BaseEndpoint) # noqa: S101 BaseEndpoint.__init__(self) self.raise_errors = raise_errors diff --git a/oauthlib/oauth2/rfc6749/errors.py b/oauthlib/oauth2/rfc6749/errors.py index 6fcb7519..3b415748 100644 --- a/oauthlib/oauth2/rfc6749/errors.py +++ b/oauthlib/oauth2/rfc6749/errors.py @@ -150,7 +150,6 @@ class FatalClientError(OAuth2Error): Instead the user should be informed of the error by the provider itself. """ - pass class InvalidRequestFatalError(FatalClientError): diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py index 858855a1..09dc6199 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -387,7 +387,7 @@ def validate_authorization_request(self, request): raise errors.MissingResponseTypeError(request=request) # Value MUST be set to "code" or one of the OpenID authorization code including # response_types "code token", "code id_token", "code token id_token" - elif not 'code' in request.response_type and request.response_type != 'none': + elif 'code' not in request.response_type and request.response_type != 'none': raise errors.UnsupportedResponseTypeError(request=request) if not self.request_validator.validate_response_type(request.client_id, @@ -400,9 +400,8 @@ def validate_authorization_request(self, request): # OPTIONAL. Validate PKCE request or reply with "error"/"invalid_request" # https://tools.ietf.org/html/rfc6749#section-4.4.1 - if self.request_validator.is_pkce_required(request.client_id, request) is True: - if request.code_challenge is None: - raise errors.MissingCodeChallengeError(request=request) + if self.request_validator.is_pkce_required(request.client_id, request) is True and request.code_challenge is None: + raise errors.MissingCodeChallengeError(request=request) if request.code_challenge is not None: request_info["code_challenge"] = request.code_challenge diff --git a/oauthlib/oauth2/rfc6749/grant_types/base.py b/oauthlib/oauth2/rfc6749/grant_types/base.py index ca343a11..d96a2db4 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/base.py +++ b/oauthlib/oauth2/rfc6749/grant_types/base.py @@ -143,7 +143,7 @@ def add_token(self, token, token_handler, request): :type request: oauthlib.common.Request """ # Only add a hybrid access token on auth step if asked for - if not request.response_type in ["token", "code token", "id_token token", "code id_token token"]: + if request.response_type not in ["token", "code token", "id_token token", "code id_token token"]: return token token.update(token_handler.create_token(request, refresh_token=False)) @@ -199,10 +199,7 @@ def prepare_authorization_response(self, request, token, headers, body, status): if request.response_type == 'none': state = token.get('state', None) - if state: - token_items = [('state', state)] - else: - token_items = [] + token_items = [('state', state)] if state else [] if request.response_mode == 'query': headers['Location'] = add_params_to_uri( diff --git a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py index e7b46189..35c54402 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py +++ b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py @@ -107,11 +107,10 @@ def validate_token_request(self, request): if not self.request_validator.authenticate_client(request): log.debug('Client authentication failed, %r.', request) raise errors.InvalidClientError(request=request) - else: - if not hasattr(request.client, 'client_id'): - raise NotImplementedError('Authenticate client must set the ' - 'request.client.client_id attribute ' - 'in authenticate_client.') + elif not hasattr(request.client, 'client_id'): + raise NotImplementedError('Authenticate client must set the ' + 'request.client.client_id attribute ' + 'in authenticate_client.') # Ensure client is authorized use of this grant type self.validate_grant_type(request) diff --git a/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/oauthlib/oauth2/rfc6749/grant_types/implicit.py index 6110b6f3..cd3bfeb6 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/implicit.py +++ b/oauthlib/oauth2/rfc6749/grant_types/implicit.py @@ -233,10 +233,7 @@ def create_token_response(self, request, token_handler): # In OIDC implicit flow it is possible to have a request_type that does not include the access_token! # "id_token token" - return the access token and the id token # "id_token" - don't return the access token - if "token" in request.response_type.split(): - token = token_handler.create_token(request, refresh_token=False) - else: - token = {} + token = token_handler.create_token(request, refresh_token=False) if 'token' in request.response_type.split() else {} if request.state is not None: token['state'] = request.state diff --git a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py index 4b0de5bf..55d92870 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py +++ b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py @@ -180,12 +180,11 @@ def validate_token_request(self, request): request.password, request.client, request): raise errors.InvalidGrantError( 'Invalid credentials given.', request=request) - else: - if not hasattr(request.client, 'client_id'): - raise NotImplementedError( - 'Validate user must set the ' - 'request.client.client_id attribute ' - 'in authenticate_client.') + elif not hasattr(request.client, 'client_id'): + raise NotImplementedError( + 'Validate user must set the ' + 'request.client.client_id attribute ' + 'in authenticate_client.') log.debug('Authorizing access to user %r.', request.user) # Ensure client is authorized use of this grant type diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 0f0f423a..f7378d81 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -45,10 +45,10 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in `Section 10.12`_. - :param code_challenge: PKCE parameter. A challenge derived from the - code_verifier that is sent in the authorization + :param code_challenge: PKCE parameter. A challenge derived from the + code_verifier that is sent in the authorization request, to be verified against later. - :param code_challenge_method: PKCE parameter. A method that was used to derive the + :param code_challenge_method: PKCE parameter. A method that was used to derive the code_challenge. Defaults to "plain" if not present in the request. :param kwargs: Extra arguments to embed in the grant/authorization URL. @@ -150,9 +150,8 @@ def prepare_token_request(grant_type, body='', include_client_id=True, code_veri # pull the `client_id` out of the kwargs. client_id = kwargs.pop('client_id', None) - if include_client_id: - if client_id is not None: - params.append(('client_id', client_id)) + if include_client_id and client_id is not None: + params.append(('client_id', client_id)) # use code_verifier if code_challenge was passed in the authorization request if code_verifier is not None: @@ -280,7 +279,7 @@ def parse_authorization_code_response(uri, state=None): if 'error' in params: raise_from_error(params.get('error'), params) - if not 'code' in params: + if 'code' not in params: raise MissingCodeError("Missing code parameter in response.") return params @@ -450,12 +449,11 @@ def validate_token_parameters(params): if 'error' in params: raise_from_error(params.get('error'), params) - if not 'access_token' in params: + if 'access_token' not in params: raise MissingTokenError(description="Missing access token parameter.") - if not 'token_type' in params: - if os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'): - raise MissingTokenTypeError() + if 'token_type' not in params and os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'): + raise MissingTokenTypeError() # If the issued access token scope is different from the one requested by # the client, the authorization server MUST include the "scope" response diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py index 3910c0b9..6d6ebaa8 100644 --- a/oauthlib/oauth2/rfc6749/request_validator.py +++ b/oauthlib/oauth2/rfc6749/request_validator.py @@ -48,12 +48,12 @@ def authenticate_client(self, request, *args, **kwargs): Headers may be accesses through request.headers and parameters found in both body and query can be obtained by direct attribute access, i.e. request.client_id for client_id in the URL query. - + The authentication process is required to contain the identification of the client (i.e. search the database based on the client_id). In case the client doesn't exist based on the received client_id, this method has to return False and the HTTP response created by the library will contain - 'invalid_client' message. + 'invalid_client' message. After the client identification succeeds, this method needs to set the client on the request, i.e. request.client = client. A client object's diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py index 0757d07e..2e8c9c4a 100644 --- a/oauthlib/oauth2/rfc6749/tokens.py +++ b/oauthlib/oauth2/rfc6749/tokens.py @@ -123,10 +123,7 @@ def prepare_mac_header(token, uri, key, http_method, sch, net, path, par, query, fra = urlparse(uri) - if query: - request_uri = path + '?' + query - else: - request_uri = path + request_uri = path + '?' + query if query else path # Hash the body/payload if body is not None and draft == 0: @@ -305,10 +302,7 @@ def create_token(self, request, refresh_token=False, **kwargs): "If you do, call `request_validator.save_token()` instead.", DeprecationWarning) - if callable(self.expires_in): - expires_in = self.expires_in(request) - else: - expires_in = self.expires_in + expires_in = self.expires_in(request) if callable(self.expires_in) else self.expires_in request.expires_in = expires_in diff --git a/oauthlib/openid/connect/core/exceptions.py b/oauthlib/openid/connect/core/exceptions.py index 099b84e2..8a3e79f4 100644 --- a/oauthlib/openid/connect/core/exceptions.py +++ b/oauthlib/openid/connect/core/exceptions.py @@ -72,8 +72,8 @@ class InvalidRequestURI(OpenIDClientError): contains invalid data. """ error = 'invalid_request_uri' - description = 'The request_uri in the Authorization Request returns an ' \ - 'error or contains invalid data.' + description = ('The request_uri in the Authorization Request returns an ' + 'error or contains invalid data.') class InvalidRequestObject(OpenIDClientError): diff --git a/oauthlib/openid/connect/core/grant_types/hybrid.py b/oauthlib/openid/connect/core/grant_types/hybrid.py index 7cb0758b..9c1fc702 100644 --- a/oauthlib/openid/connect/core/grant_types/hybrid.py +++ b/oauthlib/openid/connect/core/grant_types/hybrid.py @@ -54,10 +54,9 @@ def openid_authorization_validator(self, request): # Token. Sufficient entropy MUST be present in the `nonce` # values used to prevent attackers from guessing values. For # implementation notes, see Section 15.5.2. - if request.response_type in ["code id_token", "code id_token token"]: - if not request.nonce: - raise InvalidRequestError( - request=request, - description='Request is missing mandatory nonce parameter.' - ) + if request.response_type in ["code id_token", "code id_token token"] and not request.nonce: + raise InvalidRequestError( + request=request, + description='Request is missing mandatory nonce parameter.' + ) return request_info diff --git a/oauthlib/openid/connect/core/tokens.py b/oauthlib/openid/connect/core/tokens.py index 936ab52e..3ab35492 100644 --- a/oauthlib/openid/connect/core/tokens.py +++ b/oauthlib/openid/connect/core/tokens.py @@ -27,10 +27,7 @@ def __init__(self, request_validator=None, token_generator=None, def create_token(self, request, refresh_token=False): """Create a JWT Token, using requestvalidator method.""" - if callable(self.expires_in): - expires_in = self.expires_in(request) - else: - expires_in = self.expires_in + expires_in = self.expires_in(request) if callable(self.expires_in) else self.expires_in request.expires_in = expires_in diff --git a/oauthlib/signals.py b/oauthlib/signals.py index 8fd347a5..9538d098 100644 --- a/oauthlib/signals.py +++ b/oauthlib/signals.py @@ -7,7 +7,7 @@ try: from blinker import Namespace signals_available = True -except ImportError: # noqa +except ImportError: class Namespace: def signal(self, name, doc=None): return _FakeSignal(name, doc) @@ -26,7 +26,8 @@ def _fail(self, *args, **kwargs): raise RuntimeError('signalling support is unavailable ' 'because the blinker library is ' 'not installed.') - send = lambda *a, **kw: None + def send(*a, **kw): + return None connect = disconnect = has_receivers_for = receivers_for = \ temporarily_connected_to = connected_to = _fail del _fail diff --git a/oauthlib/uri_validate.py b/oauthlib/uri_validate.py index a6fe0fb2..69d2c950 100644 --- a/oauthlib/uri_validate.py +++ b/oauthlib/uri_validate.py @@ -174,8 +174,7 @@ URI_reference = r"^(?: %(URI)s | %(relative_ref)s )$" % locals() # absolute-URI = scheme ":" hier-part [ "?" query ] -absolute_URI = r"^(?: %(scheme)s : %(hier_part)s (?: \? %(query)s )? )$" % locals( -) +absolute_URI = r"^(?: %(scheme)s : %(hier_part)s (?: \? %(query)s )? )$" % locals() # noqa: N816 def is_uri(uri): diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..443f123e --- /dev/null +++ b/ruff.toml @@ -0,0 +1,107 @@ +# NOTE: You have to use single-quoted strings in TOML for regular expressions. +# It's the equivalent of r-strings in Python. Multiline strings are treated as +# verbose regular expressions by Black. Use [ ] to denote a significant space +# character. + +# When switching from ruff.toml to pyproject.toml, use the section names that +# start with [tool.ruff + +# [tool.ruff] +select = [ + "A", # flake8-builtins + "AIR", # Airflow + "ASYNC", # flake8-async + "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + "C90", # McCabe cyclomatic complexity + "CPY", # flake8-copyright + "DJ", # flake8-django + "E", # pycodestyle + "EXE", # flake8-executable + "F", # Pyflakes + "FA", # flake8-future-annotations + "FLY", # flynt + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + "N", # pep8-naming + "NPY", # NumPy-specific rules + "PD", # pandas-vet + "PERF", # Perflint + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # Pylint + "PT", # flake8-pytest-style + "PYI", # flake8-pyi + "RSE", # flake8-raise + "RUF", # Ruff-specific rules + "S", # flake8-bandit + "SIM", # flake8-simplify + "SLOT", # flake8-slots + "T10", # flake8-debugger + "T20", # flake8-print + "TCH", # flake8-type-checking + "W", # pycodestyle + "YTT", # flake8-2020 + # "ANN", # flake8-annotations + # "ARG", # flake8-unused-arguments + # "B", # flake8-bugbear + # "COM", # flake8-commas + # "D", # pydocstyle + # "DTZ", # flake8-datetimez + # "EM", # flake8-errmsg + # "ERA", # eradicate + # "FBT", # flake8-boolean-trap + # "FIX", # flake8-fixme + # "G", # flake8-logging-format + # "I", # isort + # "PTH", # flake8-use-pathlib + # "Q", # flake8-quotes + # "RET", # flake8-return + # "SLF", # flake8-self + # "TD", # flake8-todos + # "TID", # flake8-tidy-imports + # "TRY", # tryceratops + # "UP", # pyupgrade +] +ignore = [ + "F401", + "F403", + "F405", + "F841", + "FLY002", + "N806", + "N818", + "PERF401", + "PLW2901", + "PT008", + "RSE102", + "RUF005", + "RUF012", + "RUF013", + "S105", + "S106", + "S107", +] +line-length = 255 +target-version = "py37" + +# [tool.ruff.mccabe] +[mccabe] +max-complexity = 24 # default is 10 + +# [tool.ruff.per-file-ignores] +[per-file-ignores] +"docs/conf.py" = ["A001", "INP001"] +"oauthlib/oauth2/rfc6749/clients/base.py" = ["E722"] +"oauthlib/oauth2/rfc6749/endpoints/base.py" = ["BLE001"] +"oauthlib/openid/connect/core/grant_types/base.py" = ["BLE001"] +"tests/*" = ["PT009", "PT027", "S101"] + +# [tool.ruff.pylint] +[pylint] +allow-magic-value-types = ["int", "str"] +max-args = 16 # default is 5 +max-branches = 24 # default is 12 +max-statements = 56 # default is 50 diff --git a/setup.py b/setup.py index 4c435f9a..ab28d801 100755 --- a/setup.py +++ b/setup.py @@ -1,8 +1,9 @@ +#!/usr/bin/env python3 # Hack because logging + setuptools sucks. -try: +import contextlib +with contextlib.suppress(ImportError): import multiprocessing -except ImportError: - pass + from os.path import dirname, join diff --git a/tests/oauth1/rfc5849/endpoints/test_base.py b/tests/oauth1/rfc5849/endpoints/test_base.py index e87f359b..792aaccf 100644 --- a/tests/oauth1/rfc5849/endpoints/test_base.py +++ b/tests/oauth1/rfc5849/endpoints/test_base.py @@ -304,7 +304,7 @@ def dummy_access_token(self): def validate_timestamp_and_nonce(self, client_key, timestamp, nonce, request, request_token=None, access_token=None): resource_owner_key = request_token if request_token else access_token - return not (client_key, nonce, timestamp, resource_owner_key) in self.nonces + return (client_key, nonce, timestamp, resource_owner_key) not in self.nonces def validate_client_key(self, client_key): return client_key in self.clients diff --git a/tests/oauth1/rfc5849/test_signatures.py b/tests/oauth1/rfc5849/test_signatures.py index 2d4735ea..0dd2859a 100644 --- a/tests/oauth1/rfc5849/test_signatures.py +++ b/tests/oauth1/rfc5849/test_signatures.py @@ -82,12 +82,13 @@ class SignatureTests(TestCase): # ==== Example test vector ======================================= - eg_signature_base_string =\ - 'POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q' \ - '%26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_' \ - 'key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m' \ - 'ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk' \ + eg_signature_base_string = ( + 'POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q' + '%26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_' + 'key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m' + 'ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk' '9d7dh3k39sjv7' + ) # The _signature base string_ above is copied from the end of # RFC 5849 section 3.4.1.1. @@ -101,11 +102,11 @@ class SignatureTests(TestCase): eg_base_string_uri = 'http://example.com/request' - eg_normalized_parameters =\ - 'a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9dj' \ - 'dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1' \ + eg_normalized_parameters = ( + 'a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9dj' + 'dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1' '&oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7' - + ) # The above _normalized parameters_ corresponds to the parameters below. # # The parameters below is copied from the table at the end of @@ -133,12 +134,12 @@ class SignatureTests(TestCase): eg_body = 'c2&a3=2+q' - eg_authorization_header =\ - 'OAuth realm="Example", oauth_consumer_key="9djdj82h48djs9d2",' \ - ' oauth_token="kkk9d7dh3k39sjv7", oauth_signature_method="HMAC-SHA1",' \ - ' oauth_timestamp="137131201", oauth_nonce="7d8f3e4a",' \ + eg_authorization_header = ( + 'OAuth realm="Example", oauth_consumer_key="9djdj82h48djs9d2",' + ' oauth_token="kkk9d7dh3k39sjv7", oauth_signature_method="HMAC-SHA1",' + ' oauth_timestamp="137131201", oauth_nonce="7d8f3e4a",' ' oauth_signature="djosJKDKJSD8743243%2Fjdk33klY%3D"' - + ) # ==== Signature base string calculating function tests ========== def test_signature_base_string(self): @@ -465,10 +466,10 @@ def test_normalize_parameters(self): expected_signature_hmac_sha256 = \ 'wdfdHUKXHbOnOGZP8WFAWMSAmWzN3EVBWWgXGlC/Eo4=' - expected_signature_hmac_sha512 = \ - 'u/vlyZFDxOWOZ9UUXwRBJHvq8/T4jCA74ocRmn2ECnjUBTAeJiZIRU8hDTjS88Tz' \ + expected_signature_hmac_sha512 = ( + 'u/vlyZFDxOWOZ9UUXwRBJHvq8/T4jCA74ocRmn2ECnjUBTAeJiZIRU8hDTjS88Tz' '1fGONffMpdZxUkUTW3k1kg==' - + ) def test_sign_hmac_sha1_with_client(self): """ Test sign and verify with HMAC-SHA1. @@ -632,21 +633,21 @@ def test_hmac_false_positives(self): # Note: the "echo -n" is needed to remove the last newline character, which # most text editors will add. - expected_signature_rsa_sha1 = \ - 'mFY2KOEnlYWsTvUA+5kxuBIcvBYXu+ljw9ttVJQxKduMueGSVPCB1tK1PlqVLK738' \ - 'HK0t19ecBJfb6rMxUwrriw+MlBO+jpojkZIWccw1J4cAb4qu4M81DbpUAq4j/1w/Q' \ + expected_signature_rsa_sha1 = ( + 'mFY2KOEnlYWsTvUA+5kxuBIcvBYXu+ljw9ttVJQxKduMueGSVPCB1tK1PlqVLK738' + 'HK0t19ecBJfb6rMxUwrriw+MlBO+jpojkZIWccw1J4cAb4qu4M81DbpUAq4j/1w/Q' 'yTR4TWCODlEfN7Zfgy8+pf+TjiXfIwRC1jEWbuL1E=' - - expected_signature_rsa_sha256 = \ - 'jqKl6m0WS69tiVJV8ZQ6aQEfJqISoZkiPBXRv6Al2+iFSaDpfeXjYm+Hbx6m1azR' \ - 'drZ/35PM3cvuid3LwW/siAkzb0xQcGnTyAPH8YcGWzmnKGY7LsB7fkqThchNxvRK' \ + ) + expected_signature_rsa_sha256 = ( + 'jqKl6m0WS69tiVJV8ZQ6aQEfJqISoZkiPBXRv6Al2+iFSaDpfeXjYm+Hbx6m1azR' + 'drZ/35PM3cvuid3LwW/siAkzb0xQcGnTyAPH8YcGWzmnKGY7LsB7fkqThchNxvRK' '/N7s9M1WMnfZZ+1dQbbwtTs1TG1+iexUcV7r3M7Heec=' - - expected_signature_rsa_sha512 = \ - 'jL1CnjlsNd25qoZVHZ2oJft47IRYTjpF5CvCUjL3LY0NTnbEeVhE4amWXUFBe9GL' \ - 'DWdUh/79ZWNOrCirBFIP26cHLApjYdt4ZG7EVK0/GubS2v8wT1QPRsog8zyiMZkm' \ + ) + expected_signature_rsa_sha512 = ( + 'jL1CnjlsNd25qoZVHZ2oJft47IRYTjpF5CvCUjL3LY0NTnbEeVhE4amWXUFBe9GL' + 'DWdUh/79ZWNOrCirBFIP26cHLApjYdt4ZG7EVK0/GubS2v8wT1QPRsog8zyiMZkm' 'g4JXdWCGXG8YRvRJTg+QKhXuXwS6TcMNakrgzgFIVhA=' - + ) def test_sign_rsa_sha1_with_client(self): """ Test sign and verify with RSA-SHA1. diff --git a/tests/oauth2/rfc6749/endpoints/test_metadata.py b/tests/oauth2/rfc6749/endpoints/test_metadata.py index 1f5b9121..bcb9c0f5 100644 --- a/tests/oauth2/rfc6749/endpoints/test_metadata.py +++ b/tests/oauth2/rfc6749/endpoints/test_metadata.py @@ -130,8 +130,8 @@ def test_server_metadata(self): } def sort_list(claims): - for k in claims.keys(): - claims[k] = sorted(claims[k]) + for key, value in claims.items(): + claims[key] = sorted(value) sort_list(metadata.claims) sort_list(expected_claims) diff --git a/tests/oauth2/rfc6749/test_utils.py b/tests/oauth2/rfc6749/test_utils.py index 32995919..8417fe56 100644 --- a/tests/oauth2/rfc6749/test_utils.py +++ b/tests/oauth2/rfc6749/test_utils.py @@ -78,7 +78,7 @@ def test_list_to_scope(self): for x in string_list: assert x in set_scope - self.assertRaises(ValueError, list_to_scope, object()) + self.assertRaises(ValueError, list_to_scope, object()) def test_scope_to_list(self): expected = ['foo', 'bar', 'baz']