forked from Github-Mirrors/canaille
refactor: apply ruff migrations for python 3.10+
This commit is contained in:
parent
37adb66e06
commit
0b51b01031
10 changed files with 121 additions and 143 deletions
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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 <https://github.com/tktech/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 <https://sentry.io>`_ 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::
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue