diff --git a/canaille/app/configuration.py b/canaille/app/configuration.py index 75e2dc4e..556edef4 100644 --- a/canaille/app/configuration.py +++ b/canaille/app/configuration.py @@ -2,7 +2,6 @@ import os import smtplib import socket import sys -from typing import Optional from flask import current_app from pydantic import ValidationError @@ -51,7 +50,7 @@ class RootSettings(BaseSettings): You MUST change this. """ - SERVER_NAME: Optional[str] = None + SERVER_NAME: str | None = None """The Flask :external:py:data:`SERVER_NAME` configuration setting. This sets domain name on which canaille will be served. @@ -87,21 +86,21 @@ def settings_factory(config, env_file=".env", env_prefix=""): ): from canaille.backends.sql.configuration import SQLSettings - attributes["CANAILLE_SQL"] = (Optional[SQLSettings], None) + attributes["CANAILLE_SQL"] = ((SQLSettings | None), None) if "CANAILLE_LDAP" in config or any( var.startswith("CANAILLE__LDAP__") for var in os.environ ): from canaille.backends.ldap.configuration import LDAPSettings - attributes["CANAILLE_LDAP"] = (Optional[LDAPSettings], None) + attributes["CANAILLE_LDAP"] = ((LDAPSettings | None), None) if "CANAILLE_OIDC" in config or any( var.startswith("CANAILLE_OIDC__") for var in os.environ ): from canaille.oidc.configuration import OIDCSettings - attributes["CANAILLE_OIDC"] = (Optional[OIDCSettings], None) + attributes["CANAILLE_OIDC"] = ((OIDCSettings | None), None) Settings = create_model( "Settings", diff --git a/canaille/backends/memory/backend.py b/canaille/backends/memory/backend.py index e95ec463..36bf6bc4 100644 --- a/canaille/backends/memory/backend.py +++ b/canaille/backends/memory/backend.py @@ -2,7 +2,6 @@ import copy import datetime import uuid from typing import Any -from typing import Dict from canaille.backends import Backend @@ -12,7 +11,7 @@ def listify(value): class MemoryBackend(Backend): - indexes: Dict[str, Dict[str, Any]] = None + indexes: dict[str, dict[str, Any]] = None """Associates ids and states.""" attribute_indexes = None diff --git a/canaille/backends/models.py b/canaille/backends/models.py index 6504c783..1874ba91 100644 --- a/canaille/backends/models.py +++ b/canaille/backends/models.py @@ -4,8 +4,6 @@ import typing from collections import ChainMap from typing import Annotated from typing import ClassVar -from typing import List -from typing import Optional from typing import get_origin from typing import get_type_hints @@ -20,7 +18,7 @@ class Model: It details all the common attributes shared by every models. """ - id: Optional[str] = None + id: str | None = None """A unique identifier for a SCIM resource as defined by the service provider. Id will be :py:data:`None` until the :meth:`~canaille.backends.models.BackendModel.save` method is called. @@ -38,11 +36,11 @@ class Model: Section 9 for additional considerations regarding privacy. """ - created: Optional[datetime.datetime] = None + created: datetime.datetime | None = None """The :class:`~datetime.datetime` that the resource was added to the service provider.""" - last_modified: Optional[datetime.datetime] = None + last_modified: datetime.datetime | None = None """The most recent :class:`~datetime.datetime` that the details of this resource were updated at the service provider. @@ -50,7 +48,7 @@ class Model: the value MUST be the same as the value of :attr:`~canaille.backends.models.Model.created`. """ - _attributes: ClassVar[Optional[List[str]]] = None + _attributes: ClassVar[list[str] | None] = None @classproperty def attributes(cls): diff --git a/canaille/backends/sql/models.py b/canaille/backends/sql/models.py index 50944229..754b2113 100644 --- a/canaille/backends/sql/models.py +++ b/canaille/backends/sql/models.py @@ -1,7 +1,6 @@ import datetime import typing import uuid -from typing import List from sqlalchemy import Boolean from sqlalchemy import Column @@ -82,8 +81,8 @@ class User(canaille.core.models.User, Base, SqlAlchemyModel): given_name: Mapped[str] = mapped_column(String, nullable=True) formatted_name: Mapped[str] = mapped_column(String, nullable=True) display_name: Mapped[str] = mapped_column(String, nullable=True) - emails: Mapped[List[str]] = mapped_column(MutableJson, nullable=True) - phone_numbers: Mapped[List[str]] = mapped_column(MutableJson, nullable=True) + emails: Mapped[list[str]] = mapped_column(MutableJson, nullable=True) + phone_numbers: Mapped[list[str]] = mapped_column(MutableJson, nullable=True) formatted_address: Mapped[str] = mapped_column(String, nullable=True) street: Mapped[str] = mapped_column(String, nullable=True) postal_code: Mapped[str] = mapped_column(String, nullable=True) @@ -95,7 +94,7 @@ class User(canaille.core.models.User, Base, SqlAlchemyModel): department: Mapped[str] = mapped_column(String, nullable=True) title: Mapped[str] = mapped_column(String, nullable=True) organization: Mapped[str] = mapped_column(String, nullable=True) - groups: Mapped[List["Group"]] = relationship( + groups: Mapped[list["Group"]] = relationship( secondary=membership_association_table, back_populates="members" ) lock_date: Mapped[datetime.datetime] = mapped_column( @@ -118,7 +117,7 @@ class Group(canaille.core.models.Group, Base, SqlAlchemyModel): display_name: Mapped[str] = mapped_column(String) description: Mapped[str] = mapped_column(String, nullable=True) - members: Mapped[List["User"]] = relationship( + members: Mapped[list["User"]] = relationship( secondary=membership_association_table, back_populates="groups" ) @@ -146,10 +145,10 @@ class Client(canaille.oidc.models.Client, Base, SqlAlchemyModel): description: Mapped[str] = mapped_column(String, nullable=True) preconsent: Mapped[bool] = mapped_column(Boolean, nullable=True) - post_logout_redirect_uris: Mapped[List[str]] = mapped_column( + post_logout_redirect_uris: Mapped[list[str]] = mapped_column( MutableJson, nullable=True ) - audience: Mapped[List["Client"]] = relationship( + audience: Mapped[list["Client"]] = relationship( "Client", secondary=client_audience_association_table, primaryjoin=id == client_audience_association_table.c.client_id, @@ -164,13 +163,13 @@ class Client(canaille.oidc.models.Client, Base, SqlAlchemyModel): TZDateTime(timezone=True), nullable=True ) client_name: Mapped[str] = mapped_column(String, nullable=True) - contacts: Mapped[List[str]] = mapped_column(MutableJson, nullable=True) + contacts: Mapped[list[str]] = mapped_column(MutableJson, nullable=True) client_uri: Mapped[str] = mapped_column(String, nullable=True) - redirect_uris: Mapped[List[str]] = mapped_column(MutableJson, nullable=True) + redirect_uris: Mapped[list[str]] = mapped_column(MutableJson, nullable=True) logo_uri: Mapped[str] = mapped_column(String, nullable=True) - grant_types: Mapped[List[str]] = mapped_column(MutableJson, nullable=True) - response_types: Mapped[List[str]] = mapped_column(MutableJson, nullable=True) - scope: Mapped[List[str]] = mapped_column(MutableJson, nullable=True) + grant_types: Mapped[list[str]] = mapped_column(MutableJson, nullable=True) + response_types: Mapped[list[str]] = mapped_column(MutableJson, nullable=True) + scope: Mapped[list[str]] = mapped_column(MutableJson, nullable=True) tos_uri: Mapped[str] = mapped_column(String, nullable=True) policy_uri: Mapped[str] = mapped_column(String, nullable=True) jwks_uri: Mapped[str] = mapped_column(String, nullable=True) @@ -201,7 +200,7 @@ class AuthorizationCode(canaille.oidc.models.AuthorizationCode, Base, SqlAlchemy subject: Mapped["User"] = relationship() redirect_uri: Mapped[str] = mapped_column(String, nullable=True) response_type: Mapped[str] = mapped_column(String, nullable=True) - scope: Mapped[List[str]] = mapped_column(MutableJson, nullable=True) + scope: Mapped[list[str]] = mapped_column(MutableJson, nullable=True) nonce: Mapped[str] = mapped_column(String, nullable=True) issue_date: Mapped[datetime.datetime] = mapped_column( TZDateTime(timezone=True), nullable=True @@ -243,7 +242,7 @@ class Token(canaille.oidc.models.Token, Base, SqlAlchemyModel): subject: Mapped["User"] = relationship() type: Mapped[str] = mapped_column(String, nullable=True) refresh_token: Mapped[str] = mapped_column(String, nullable=True) - scope: Mapped[List[str]] = mapped_column(MutableJson, nullable=True) + scope: Mapped[list[str]] = mapped_column(MutableJson, nullable=True) issue_date: Mapped[datetime.datetime] = mapped_column( TZDateTime(timezone=True), nullable=True ) @@ -251,7 +250,7 @@ class Token(canaille.oidc.models.Token, Base, SqlAlchemyModel): revokation_date: Mapped[datetime.datetime] = mapped_column( TZDateTime(timezone=True), nullable=True ) - audience: Mapped[List["Client"]] = relationship( + audience: Mapped[list["Client"]] = relationship( "Client", secondary=token_audience_association_table, primaryjoin=id == token_audience_association_table.c.token_id, @@ -277,7 +276,7 @@ class Consent(canaille.oidc.models.Consent, Base, SqlAlchemyModel): subject: Mapped["User"] = relationship() client_id: Mapped[str] = mapped_column(ForeignKey("client.id")) client: Mapped["Client"] = relationship() - scope: Mapped[List[str]] = mapped_column(MutableJson, nullable=True) + scope: Mapped[list[str]] = mapped_column(MutableJson, nullable=True) issue_date: Mapped[datetime.datetime] = mapped_column( TZDateTime(timezone=True), nullable=True ) diff --git a/canaille/core/configuration.py b/canaille/core/configuration.py index d21ea947..c8100e4d 100644 --- a/canaille/core/configuration.py +++ b/canaille/core/configuration.py @@ -1,8 +1,4 @@ from enum import Enum -from typing import Dict -from typing import List -from typing import Optional -from typing import Union from pydantic import BaseModel from pydantic import ValidationInfo @@ -18,25 +14,25 @@ class SMTPSettings(BaseModel): authentication. """ - HOST: Optional[str] = "localhost" + HOST: str | None = "localhost" """The SMTP host.""" - PORT: Optional[int] = 25 + PORT: int | None = 25 """The SMTP port.""" - TLS: Optional[bool] = False + TLS: bool | None = False """Whether to use TLS to connect to the SMTP server.""" - SSL: Optional[bool] = False + SSL: bool | None = False """Whether to use SSL to connect to the SMTP server.""" - LOGIN: Optional[str] = None + LOGIN: str | None = None """The SMTP login.""" - PASSWORD: Optional[str] = None + PASSWORD: str | None = None """The SMTP password.""" - FROM_ADDR: Optional[str] = None + FROM_ADDR: str | None = None """The sender for Canaille mails. Some mail provider might require a valid sender address. @@ -83,7 +79,7 @@ class ACLSettings(BaseModel): to :attr:`READ` and :attr:`WRITE`. Users matching several filters will cumulate permissions. """ - PERMISSIONS: List[Permission] = [Permission.EDIT_SELF, Permission.USE_OIDC] + PERMISSIONS: list[Permission] = [Permission.EDIT_SELF, Permission.USE_OIDC] """A list of :class:`Permission` users in the access control will be able to manage. For example:: @@ -95,7 +91,7 @@ class ACLSettings(BaseModel): "impersonate_users", ]""" - READ: List[str] = [ + READ: list[str] = [ "user_name", "groups", "lock_date", @@ -103,7 +99,7 @@ class ACLSettings(BaseModel): """A list of :class:`~canaille.core.models.User` attributes that users in the ACL will be able to read.""" - WRITE: List[str] = [ + WRITE: list[str] = [ "photo", "given_name", "family_name", @@ -129,9 +125,9 @@ class ACLSettings(BaseModel): @field_validator("READ") def validate_read_values( cls, - read: List[str], + read: list[str], info: ValidationInfo, - ) -> List[str]: + ) -> list[str]: from canaille.core.models import User assert all(field in User.attributes for field in read) @@ -140,15 +136,15 @@ class ACLSettings(BaseModel): @field_validator("WRITE") def validate_write_values( cls, - write: List[str], + write: list[str], info: ValidationInfo, - ) -> List[str]: + ) -> list[str]: from canaille.core.models import User assert all(field in User.attributes for field in write) return write - FILTER: Optional[Union[Dict[str, str], List[Dict[str, str]]]] = None + FILTER: dict[str, str] | list[dict[str, str]] | None = None """:attr:`FILTER` can be: - :py:data:`None`, in which case all the users will match this access control @@ -181,11 +177,11 @@ class CoreSettings(BaseModel): Used for display purpose. """ - LOGO: Optional[str] = None + LOGO: str | None = None """The logo of your organization, this is useful to make your organization recognizable on login screens.""" - FAVICON: Optional[str] = None + FAVICON: str | None = None """You favicon. If unset and :attr:`LOGO` is set, then the logo will be used. @@ -197,20 +193,20 @@ class CoreSettings(BaseModel): Defaults to ``default``. Theming is done with `flask-themer `_. """ - LANGUAGE: Optional[str] = None + LANGUAGE: str | None = None """If a language code is set, it will be used for every user. If unset, the language is guessed according to the users browser. """ - TIMEZONE: Optional[str] = None + TIMEZONE: str | None = None """The timezone in which datetimes will be displayed to the users (e.g. ``CEST``). If unset, the server timezone will be used. """ - SENTRY_DSN: Optional[str] = None + SENTRY_DSN: str | None = None """A `Sentry `_ DSN to collect the exceptions. This is useful for tracking errors in test and production environments. @@ -259,7 +255,7 @@ class CoreSettings(BaseModel): Defaults to 2 days. """ - LOGGING: Optional[Union[str, Dict]] = None + LOGGING: str | dict | None = None """Configures the logging output using the python logging configuration format: - if :py:data:`None`, everything is logged in the standard error output @@ -282,14 +278,14 @@ class CoreSettings(BaseModel): formatter = "default" """ - SMTP: Optional[SMTPSettings] = None + SMTP: SMTPSettings | None = None """The settings related to SMTP and mail configuration. If unset, mail-related features like password recovery won't be enabled. """ - ACL: Optional[Dict[str, ACLSettings]] = {"DEFAULT": ACLSettings()} + ACL: dict[str, ACLSettings] | None = {"DEFAULT": ACLSettings()} """Mapping of permission groups. See :class:`ACLSettings` for more details. The ACL name can be freely chosen. For example:: diff --git a/canaille/core/endpoints/account.py b/canaille/core/endpoints/account.py index d020f8e3..e69f9639 100644 --- a/canaille/core/endpoints/account.py +++ b/canaille/core/endpoints/account.py @@ -4,7 +4,6 @@ import io from dataclasses import astuple from dataclasses import dataclass from importlib import metadata -from typing import List import wtforms from flask import Blueprint @@ -189,7 +188,7 @@ class RegistrationPayload(VerificationPayload): user_name: str user_name_editable: bool email: str - groups: List[str] + groups: list[str] @bp.route("/invite", methods=["GET", "POST"]) diff --git a/canaille/core/models.py b/canaille/core/models.py index f5b44605..a532c76c 100644 --- a/canaille/core/models.py +++ b/canaille/core/models.py @@ -1,8 +1,6 @@ import datetime from typing import Annotated from typing import ClassVar -from typing import List -from typing import Optional from flask import current_app @@ -37,7 +35,7 @@ class User(Model): and is case insensitive. """ - password: Optional[str] = None + password: str | None = None """ This attribute is intended to be used as a means to set, replace, or compare (i.e., filter for equality) a password. The cleartext @@ -82,7 +80,7 @@ class User(Model): "never"). """ - preferred_language: Optional[str] = None + preferred_language: str | None = None """Indicates the user's preferred written or spoken languages and is generally used for selecting a localized user interface. @@ -98,20 +96,20 @@ class User(Model): negotiation cannot take place. """ - family_name: Optional[str] = None + family_name: str | None = None """The family name of the User, or last name in most Western languages (e.g., "Jensen" given the full name "Ms. Barbara Jane Jensen, III").""" - given_name: Optional[str] = None + given_name: str | None = None """The given name of the User, or first name in most Western languages (e.g., "Barbara" given the full name "Ms. Barbara Jane Jensen, III").""" - formatted_name: Optional[str] = None + formatted_name: str | None = None """The full name, including all middle names, titles, and suffixes as appropriate, formatted for display (e.g., "Ms. Barbara Jane Jensen, III").""" - display_name: Optional[str] = None + display_name: str | None = None """The name of the user, suitable for display to end-users. Each user returned MAY include a non-empty displayName value. The @@ -123,7 +121,7 @@ class User(Model): when presenting it to end-users. """ - emails: List[str] = [] + emails: list[str] = [] """Email addresses for the User. The value SHOULD be specified according to [RFC5321]. Service @@ -137,7 +135,7 @@ class User(Model): at the discretion of SCIM clients. """ - phone_numbers: List[str] = [] + phone_numbers: list[str] = [] """Phone numbers for the user. The value SHOULD be specified according to the format defined in @@ -150,14 +148,14 @@ class User(Model): defined by the SCIM clients. """ - formatted_address: Optional[str] = None + formatted_address: str | None = None """The full mailing address, formatted for display or use with a mailing label. This attribute MAY contain newlines. """ - street: Optional[str] = None + street: str | None = None """The full street address component, which may include house number, street name, P.O. @@ -165,16 +163,16 @@ class User(Model): attribute MAY contain newlines. """ - postal_code: Optional[str] = None + postal_code: str | None = None """The zip code or postal code component.""" - locality: Optional[str] = None + locality: str | None = None """The city or locality component.""" - region: Optional[str] = None + region: str | None = None """The state or region component.""" - photo: Optional[str] = None + photo: str | None = None """A URI that is a uniform resource locator (as defined in Section 1.1.3 of [RFC3986]) that points to a resource location representing the user's image. @@ -192,7 +190,7 @@ class User(Model): sizes: "photo" and "thumbnail". """ - profile_url: Optional[str] = None + profile_url: str | None = None """A URI that is a uniform resource locator (as defined in Section 1.1.3 of [RFC3986]) and that points to a location representing the user's online profile (e.g., a web page). @@ -200,21 +198,21 @@ class User(Model): URIs are canonicalized per Section 6.2 of [RFC3986]. """ - title: Optional[str] = None + title: str | None = None """The user's title, such as "Vice President".""" - organization: Optional[str] = None + organization: str | None = None """Identifies the name of an organization.""" - employee_number: Optional[str] = None + employee_number: str | None = None """A string identifier, typically numeric or alphanumeric, assigned to a person, typically based on order of hire or association with an organization.""" - department: Optional[str] = None + department: str | None = None """Identifies the name of a department.""" - groups: List[Annotated["Group", {"backref": "members"}]] = [] + groups: list[Annotated["Group", {"backref": "members"}]] = [] """A list of groups to which the user belongs, either through direct membership, through nested groups, or dynamically calculated. @@ -240,7 +238,7 @@ class User(Model): "readOnly". """ - lock_date: Optional[datetime.datetime] = None + lock_date: datetime.datetime | None = None """A DateTime indicating when the resource was locked.""" _readable_fields = None @@ -334,7 +332,7 @@ class Group(Model): REQUIRED. """ - members: List[Annotated["User", {"backref": "groups"}]] = [] + members: list[Annotated["User", {"backref": "groups"}]] = [] """A list of members of the Group. While values MAY be added or removed, sub-attributes of members are @@ -348,4 +346,4 @@ class Group(Model): "Group" resource schema. """ - description: Optional[str] = None + description: str | None = None diff --git a/canaille/oidc/basemodels.py b/canaille/oidc/basemodels.py index 17021c84..b33be445 100644 --- a/canaille/oidc/basemodels.py +++ b/canaille/oidc/basemodels.py @@ -1,7 +1,5 @@ import datetime from typing import ClassVar -from typing import List -from typing import Optional from canaille.backends.models import Model from canaille.core.models import User @@ -18,11 +16,11 @@ class Client(Model): identifier_attribute: ClassVar[str] = "client_id" - description: Optional[str] = None - preconsent: Optional[bool] = False - audience: List["Client"] = [] + description: str | None = None + preconsent: bool | None = False + audience: list["Client"] = [] - client_id: Optional[str] + client_id: str | None """REQUIRED. OAuth 2.0 client identifier string. It SHOULD NOT be currently @@ -31,7 +29,7 @@ class Client(Model): a registered client at its discretion. """ - client_secret: Optional[str] = None + client_secret: str | None = None """OPTIONAL. OAuth 2.0 client secret string. If issued, this MUST be unique for @@ -41,7 +39,7 @@ class Client(Model): described in OAuth 2.0 [RFC6749], Section 2.3.1. """ - client_id_issued_at: Optional[datetime.datetime] = None + client_id_issued_at: datetime.datetime | None = None """OPTIONAL. Time at which the client identifier was issued. The time is @@ -49,7 +47,7 @@ class Client(Model): measured in UTC until the date/time of issuance. """ - client_secret_expires_at: Optional[datetime.datetime] = None + client_secret_expires_at: datetime.datetime | None = None """REQUIRED if "client_secret" is issued. Time at which the client secret will expire or 0 if it will not @@ -58,7 +56,7 @@ class Client(Model): expiration. """ - redirect_uris: List[str] = [] + redirect_uris: list[str] = [] """Array of redirection URI strings for use in redirect-based flows such as the authorization code and implicit flows. @@ -68,7 +66,7 @@ class Client(Model): redirect-based flows MUST implement support for this metadata value. """ - token_endpoint_auth_method: Optional[str] = None + token_endpoint_auth_method: str | None = None """String indicator of the requested authentication method for the token endpoint. Values defined by this specification are: @@ -89,7 +87,7 @@ class Client(Model): authentication scheme as specified in Section 2.3.1 of OAuth 2.0. """ - grant_types: List[str] = ["authorization_code", "refresh_token"] + grant_types: list[str] = ["authorization_code", "refresh_token"] """Array of OAuth 2.0 grant type strings that the client can use at the token endpoint. These grant types are defined as follows: @@ -125,7 +123,7 @@ class Client(Model): client will use only the "authorization_code" Grant Type. """ - response_types: List[str] = [] + response_types: list[str] = [] """ Array of the OAuth 2.0 response type strings that the client can use at the authorization endpoint. These response types are @@ -146,7 +144,7 @@ class Client(Model): default is that the client will use only the "code" response type. """ - client_name: Optional[str] = None + client_name: str | None = None """Human-readable string name of the client to be presented to the end-user during authorization. @@ -156,7 +154,7 @@ class Client(Model): internationalized, as described in Section 2.2. """ - client_uri: Optional[str] = None + client_uri: str | None = None """URL string of a web page providing information about the client. If present, the server SHOULD display this URL to the end-user in a @@ -166,7 +164,7 @@ class Client(Model): Section 2.2. """ - logo_uri: Optional[str] = None + logo_uri: str | None = None """URL string that references a logo for the client. If present, the server SHOULD display this image to the end-user @@ -175,7 +173,7 @@ class Client(Model): described in Section 2.2. """ - scope: List[str] = [] + scope: list[str] = [] """String containing a space-separated list of scope values (as described in Section 3.3 of OAuth 2.0 [RFC6749]) that the client can use when requesting access tokens. @@ -185,7 +183,7 @@ class Client(Model): default set of scopes. """ - contacts: List[str] = None + contacts: list[str] = None """Array of strings representing ways to contact people responsible for this client, typically email addresses. @@ -194,7 +192,7 @@ class Client(Model): information on Privacy Considerations. """ - tos_uri: Optional[str] = None + tos_uri: str | None = None """URL string that points to a human-readable terms of service document for the client that describes a contractual relationship between the end-user and the client that the end-user accepts when authorizing the client. @@ -205,7 +203,7 @@ class Client(Model): described in Section 2.2. """ - policy_uri: Optional[str] = None + policy_uri: str | None = None """URL string that points to a human-readable privacy policy document that describes how the deployment organization collects, uses, retains, and discloses personal data. @@ -216,7 +214,7 @@ class Client(Model): described in Section 2.2. """ - jwks_uri: Optional[str] = None + jwks_uri: str | None = None """URL string referencing the client's JSON Web Key (JWK) Set [RFC7517] document, which contains the client's public keys. @@ -230,7 +228,7 @@ class Client(Model): parameters MUST NOT both be present in the same request or response. """ - jwk: Optional[str] = None + jwk: str | None = None """Client's JSON Web Key Set [RFC7517] document value, which contains the client's public keys. @@ -242,7 +240,7 @@ class Client(Model): parameters MUST NOT both be present in the same request or response. """ - software_id: Optional[str] = None + software_id: str | None = None """A unique identifier string (e.g., a Universally Unique Identifier (UUID)) assigned by the client developer or software publisher used by registration endpoints to identify the client software to be dynamically @@ -257,7 +255,7 @@ class Client(Model): authorization server. """ - software_version: Optional[str] = None + software_version: str | None = None """A version identifier string for the client software identified by "software_id". @@ -273,7 +271,7 @@ class Client(Model): itself and is outside the scope of this specification. """ - post_logout_redirect_uris: List[str] = [] + post_logout_redirect_uris: list[str] = [] """OPTIONAL. Array of URLs supplied by the RP to which it MAY request that the @@ -296,14 +294,14 @@ class AuthorizationCode(Model): code: str client: "Client" subject: User - redirect_uri: Optional[str] - response_type: Optional[str] - scope: List[str] - nonce: Optional[str] + redirect_uri: str | None + response_type: str | None + scope: list[str] + nonce: str | None issue_date: datetime.datetime lifetime: int - challenge: Optional[str] - challenge_method: Optional[str] + challenge: str | None + challenge_method: str | None revokation_date: datetime.datetime @@ -318,11 +316,11 @@ class Token(Model): subject: User type: str refresh_token: str - scope: List[str] + scope: list[str] issue_date: datetime.datetime lifetime: int revokation_date: datetime.datetime - audience: List["Client"] + audience: list["Client"] class Consent(Model): @@ -333,7 +331,7 @@ class Consent(Model): consent_id: str subject: User client: "Client" - scope: List[str] + scope: list[str] issue_date: datetime.datetime revokation_date: datetime.datetime diff --git a/canaille/oidc/configuration.py b/canaille/oidc/configuration.py index 09fafd09..0899cdbe 100644 --- a/canaille/oidc/configuration.py +++ b/canaille/oidc/configuration.py @@ -1,6 +1,3 @@ -from typing import List -from typing import Optional - from pydantic import BaseModel @@ -11,37 +8,33 @@ class JWTMappingSettings(BaseModel): A ``user`` var is available. """ - SUB: Optional[str] = "{{ user.user_name }}" - NAME: Optional[str] = ( + SUB: str | None = "{{ user.user_name }}" + NAME: str | None = ( "{% if user.formatted_name %}{{ user.formatted_name }}{% endif %}" ) - PHONE_NUMBER: Optional[str] = ( + PHONE_NUMBER: str | None = ( "{% if user.phone_numbers %}{{ user.phone_numbers[0] }}{% endif %}" ) - EMAIL: Optional[str] = ( + EMAIL: str | None = ( "{% if user.preferred_email %}{{ user.preferred_email }}{% endif %}" ) - GIVEN_NAME: Optional[str] = ( - "{% if user.given_name %}{{ user.given_name }}{% endif %}" - ) - FAMILY_NAME: Optional[str] = ( + GIVEN_NAME: str | None = "{% if user.given_name %}{{ user.given_name }}{% endif %}" + FAMILY_NAME: str | None = ( "{% if user.family_name %}{{ user.family_name }}{% endif %}" ) - PREFERRED_USERNAME: Optional[str] = ( + PREFERRED_USERNAME: str | None = ( "{% if user.display_name %}{{ user.display_name }}{% endif %}" ) - LOCALE: Optional[str] = ( + LOCALE: str | None = ( "{% if user.preferred_language %}{{ user.preferred_language }}{% endif %}" ) - ADDRESS: Optional[str] = ( + ADDRESS: str | None = ( "{% if user.formatted_address %}{{ user.formatted_address }}{% endif %}" ) - PICTURE: Optional[str] = ( + PICTURE: str | None = ( "{% if user.photo %}{{ url_for('core.account.photo', user=user, field='photo', _external=True) }}{% endif %}" ) - WEBSITE: Optional[str] = ( - "{% if user.profile_url %}{{ user.profile_url }}{% endif %}" - ) + WEBSITE: str | None = "{% if user.profile_url %}{{ user.profile_url }}{% endif %}" class JWTSettings(BaseModel): @@ -53,19 +46,19 @@ class JWTSettings(BaseModel): openssl rsa -in private.pem -pubout -outform PEM -out public.pem """ - PRIVATE_KEY: Optional[str] = None + PRIVATE_KEY: str | None = None """The private key. If :py:data:`None` and debug mode is enabled, then an in-memory key will be used. """ - PUBLIC_KEY: Optional[str] = None + PUBLIC_KEY: str | None = None """The public key. If :py:data:`None` and debug mode is enabled, then an in-memory key will be used. """ - ISS: Optional[str] = None + ISS: str | None = None """The URI of the identity provider.""" KTY: str = "RSA" @@ -77,7 +70,7 @@ class JWTSettings(BaseModel): EXP: int = 3600 """The time the JWT will be valid, in seconds.""" - MAPPING: Optional[JWTMappingSettings] = JWTMappingSettings() + MAPPING: JWTMappingSettings | None = JWTMappingSettings() class OIDCSettings(BaseModel): @@ -94,7 +87,7 @@ class OIDCSettings(BaseModel): :attr:`DYNAMIC_CLIENT_REGISTRATION_TOKENS`. """ - DYNAMIC_CLIENT_REGISTRATION_TOKENS: Optional[List[str]] = None + DYNAMIC_CLIENT_REGISTRATION_TOKENS: list[str] | None = None """A list of tokens that can be used for dynamic client registration.""" REQUIRE_NONCE: bool = True diff --git a/canaille/oidc/models.py b/canaille/oidc/models.py index e4c86783..034d1348 100644 --- a/canaille/oidc/models.py +++ b/canaille/oidc/models.py @@ -1,6 +1,5 @@ import datetime from typing import ClassVar -from typing import List from authlib.oauth2.rfc6749 import AuthorizationCodeMixin from authlib.oauth2.rfc6749 import ClientMixin @@ -17,14 +16,14 @@ from .basemodels import Token as BaseToken class Client(BaseClient, ClientMixin): - client_info_attributes: ClassVar[List[str]] = [ + client_info_attributes: ClassVar[list[str]] = [ "client_id", "client_secret", "client_id_issued_at", "client_secret_expires_at", ] - client_metadata_attributes: ClassVar[List[str]] = [ + client_metadata_attributes: ClassVar[list[str]] = [ "client_name", "contacts", "client_uri",