forked from Github-Mirrors/canaille
chore: add docformatter pre-commit
This commit is contained in:
parent
e8b620588e
commit
395b6ab4f3
14 changed files with 412 additions and 538 deletions
|
@ -20,6 +20,10 @@ repos:
|
|||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ["--application-directories", "canaille"]
|
||||
- repo: https://github.com/PyCQA/docformatter
|
||||
rev: v1.7.5
|
||||
hooks:
|
||||
- id: docformatter
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.0
|
||||
hooks:
|
||||
|
|
|
@ -26,9 +26,7 @@ def with_backendcontext(func):
|
|||
@with_appcontext
|
||||
@with_backendcontext
|
||||
def check():
|
||||
"""
|
||||
Check the configuration file.
|
||||
"""
|
||||
"""Check the configuration file."""
|
||||
from canaille.app.configuration import ConfigurationException
|
||||
from canaille.app.configuration import validate
|
||||
|
||||
|
@ -42,9 +40,7 @@ def check():
|
|||
@click.command()
|
||||
@with_appcontext
|
||||
def install():
|
||||
"""
|
||||
Installs canaille elements from the configuration.
|
||||
"""
|
||||
"""Installs canaille elements from the configuration."""
|
||||
from canaille.app.installation import install
|
||||
from canaille.app.configuration import ConfigurationException
|
||||
|
||||
|
|
|
@ -15,10 +15,8 @@ class ConfigurationException(Exception):
|
|||
|
||||
|
||||
def parse_file_keys(config):
|
||||
"""
|
||||
Replaces configuration entries with the '_FILE' suffix with
|
||||
the matching file content.
|
||||
"""
|
||||
"""Replaces configuration entries with the '_FILE' suffix with the matching
|
||||
file content."""
|
||||
|
||||
SUFFIX = "_FILE"
|
||||
new_config = {}
|
||||
|
|
|
@ -32,9 +32,7 @@ def current_user():
|
|||
|
||||
|
||||
def login_user(user):
|
||||
"""
|
||||
Opens a session for the user.
|
||||
"""
|
||||
"""Opens a session for the user."""
|
||||
g.user = user
|
||||
try:
|
||||
previous = (
|
||||
|
@ -48,9 +46,7 @@ def login_user(user):
|
|||
|
||||
|
||||
def logout_user():
|
||||
"""
|
||||
Closes the user session.
|
||||
"""
|
||||
"""Closes the user session."""
|
||||
try:
|
||||
session["user_id"].pop()
|
||||
del g.user
|
||||
|
|
|
@ -71,9 +71,7 @@ class HTMXFormMixin:
|
|||
render_field_extra_context = {}
|
||||
|
||||
def field_from_name(self, field_name):
|
||||
"""
|
||||
Returns a tuple containing a field and its rendering context
|
||||
"""
|
||||
"""Returns a tuple containing a field and its rendering context."""
|
||||
if self.SEPARATOR not in field_name:
|
||||
field = self[field_name] if field_name in self else None
|
||||
return field, {}
|
||||
|
@ -89,10 +87,11 @@ class HTMXFormMixin:
|
|||
return fieldlist[indice], context
|
||||
|
||||
def validate(self, *args, **kwargs):
|
||||
"""
|
||||
If the request is a HTMX request, this will only render the field
|
||||
that triggered the request (after having validated the form). This
|
||||
uses the Flask abort method to interrupt the flow with an exception.
|
||||
"""If the request is a HTMX request, this will only render the field
|
||||
that triggered the request (after having validated the form).
|
||||
|
||||
This uses the Flask abort method to interrupt the flow with an
|
||||
exception.
|
||||
"""
|
||||
if not request_is_htmx():
|
||||
return super().validate(*args, **kwargs)
|
||||
|
@ -120,10 +119,8 @@ class HTMXFormMixin:
|
|||
abort(response)
|
||||
|
||||
def form_control(self):
|
||||
"""
|
||||
Checks wether the current request is the result of the users
|
||||
adding or removing a field from a FieldList.
|
||||
"""
|
||||
"""Checks wether the current request is the result of the users adding
|
||||
or removing a field from a FieldList."""
|
||||
FIELDLIST_ADD_BUTTON = "fieldlist_add"
|
||||
FIELDLIST_REMOVE_BUTTON = "fieldlist_remove"
|
||||
|
||||
|
|
|
@ -34,36 +34,29 @@ class BaseBackend:
|
|||
|
||||
@classmethod
|
||||
def install(self, config):
|
||||
"""
|
||||
This methods prepares the database to host canaille data.
|
||||
"""
|
||||
"""This methods prepares the database to host canaille data."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def setup(self):
|
||||
"""
|
||||
This method will be called before each http request,
|
||||
it should open the connection to the backend.
|
||||
"""
|
||||
"""This method will be called before each http request, it should open
|
||||
the connection to the backend."""
|
||||
|
||||
def teardown(self):
|
||||
"""
|
||||
This method will be called after each http request,
|
||||
it should close the connections to the backend.
|
||||
"""
|
||||
"""This method will be called after each http request, it should close
|
||||
the connections to the backend."""
|
||||
|
||||
@classmethod
|
||||
def validate(cls, config):
|
||||
"""
|
||||
This method should validate the config part dedicated to the backend.
|
||||
"""This method should validate the config part dedicated to the
|
||||
backend.
|
||||
|
||||
It should raise :class:`~canaille.configuration.ConfigurationError` when
|
||||
errors are met.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def has_account_lockability(self):
|
||||
"""
|
||||
Indicates wether the backend supports locking user accounts.
|
||||
"""
|
||||
"""Indicates wether the backend supports locking user accounts."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def register_models(self):
|
||||
|
|
|
@ -4,9 +4,7 @@ from canaille.app import classproperty
|
|||
|
||||
|
||||
class Model:
|
||||
"""
|
||||
Model abstract class.
|
||||
"""
|
||||
"""Model abstract class."""
|
||||
|
||||
@classproperty
|
||||
def attributes(cls):
|
||||
|
@ -36,44 +34,37 @@ class Model:
|
|||
|
||||
@classmethod
|
||||
def fuzzy(cls, query, attributes=None, **kwargs):
|
||||
"""
|
||||
Works like :meth:`~canaille.backends.models.query` but attribute values
|
||||
loosely be matched.
|
||||
"""
|
||||
"""Works like :meth:`~canaille.backends.models.query` but attribute
|
||||
values loosely be matched."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def get(cls, identifier=None, **kwargs):
|
||||
"""
|
||||
Works like :meth:`~canaille.backends.models.query` but return only one
|
||||
element or :const:`None` if no item is matching.
|
||||
"""
|
||||
"""Works like :meth:`~canaille.backends.models.query` but return only
|
||||
one element or :const:`None` if no item is matching."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
"""
|
||||
Returns a unique value that will be used to identify the model instance.
|
||||
This value will be used in URLs in canaille, so it should be unique and short.
|
||||
"""Returns a unique value that will be used to identify the model
|
||||
instance.
|
||||
|
||||
This value will be used in URLs in canaille, so it should be
|
||||
unique and short.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Validates the current modifications in the database.
|
||||
"""
|
||||
"""Validates the current modifications in the database."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Removes the current instance from the database.
|
||||
"""
|
||||
"""Removes the current instance from the database."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""
|
||||
Assign a whole dict to the current instance. This is useful to update
|
||||
models based on forms.
|
||||
"""Assign a whole dict to the current instance. This is useful to
|
||||
update models based on forms.
|
||||
|
||||
>>> user = User.get(user_name="george")
|
||||
>>> user.first_name
|
||||
|
@ -89,8 +80,7 @@ class Model:
|
|||
setattr(self, attribute, value)
|
||||
|
||||
def reload(self):
|
||||
"""
|
||||
Cancels the unsaved modifications.
|
||||
"""Cancels the unsaved modifications.
|
||||
|
||||
>>> user = User.get(user_name="george")
|
||||
>>> user.display_name
|
||||
|
|
|
@ -13,9 +13,7 @@ except ImportError:
|
|||
@click.pass_context
|
||||
@with_appcontext
|
||||
def populate(ctx, nb):
|
||||
"""
|
||||
Populate the database with generated random data.
|
||||
"""
|
||||
"""Populate the database with generated random data."""
|
||||
ctx.ensure_object(dict)
|
||||
|
||||
ctx.obj["number"] = nb
|
||||
|
@ -26,9 +24,7 @@ def populate(ctx, nb):
|
|||
@with_appcontext
|
||||
@with_backendcontext
|
||||
def users(ctx):
|
||||
"""
|
||||
Populate the database with generated random users.
|
||||
"""
|
||||
"""Populate the database with generated random users."""
|
||||
|
||||
from canaille.core.populate import fake_users
|
||||
|
||||
|
@ -45,9 +41,7 @@ def users(ctx):
|
|||
@with_appcontext
|
||||
@with_backendcontext
|
||||
def groups(ctx, nb_users_max):
|
||||
"""
|
||||
Populate the database with generated random groups.
|
||||
"""
|
||||
"""Populate the database with generated random groups."""
|
||||
|
||||
from canaille.core.populate import fake_groups
|
||||
|
||||
|
|
|
@ -18,32 +18,32 @@ class User:
|
|||
"""
|
||||
|
||||
id: str
|
||||
"""
|
||||
A unique identifier for a SCIM resource as defined by the service
|
||||
provider. Each representation of the resource MUST include a
|
||||
non-empty "id" value. This identifier MUST be unique across the
|
||||
SCIM service provider's entire set of resources. It MUST be a
|
||||
stable, non-reassignable identifier that does not change when the
|
||||
same resource is returned in subsequent requests. The value of
|
||||
the "id" attribute is always issued by the service provider and
|
||||
MUST NOT be specified by the client. The string "bulkId" is a
|
||||
reserved keyword and MUST NOT be used within any unique identifier
|
||||
value. The attribute characteristics are "caseExact" as "true", a
|
||||
mutability of "readOnly", and a "returned" characteristic of
|
||||
"always". See Section 9 for additional considerations regarding
|
||||
privacy.
|
||||
"""A unique identifier for a SCIM resource as defined by the service
|
||||
provider.
|
||||
|
||||
Each representation of the resource MUST include a non-empty "id"
|
||||
value. This identifier MUST be unique across the SCIM service
|
||||
provider's entire set of resources. It MUST be a stable, non-
|
||||
reassignable identifier that does not change when the same resource
|
||||
is returned in subsequent requests. The value of the "id" attribute
|
||||
is always issued by the service provider and MUST NOT be specified
|
||||
by the client. The string "bulkId" is a reserved keyword and MUST
|
||||
NOT be used within any unique identifier value. The attribute
|
||||
characteristics are "caseExact" as "true", a mutability of
|
||||
"readOnly", and a "returned" characteristic of "always". See
|
||||
Section 9 for additional considerations regarding privacy.
|
||||
"""
|
||||
|
||||
user_name: Optional[str]
|
||||
"""
|
||||
A service provider's unique identifier for the user, typically
|
||||
used by the user to directly authenticate to the service provider.
|
||||
"""A service provider's unique identifier for the user, typically used by
|
||||
the user to directly authenticate to the service provider.
|
||||
|
||||
Often displayed to the user as their unique identifier within the
|
||||
system (as opposed to "id" or "externalId", which are generally
|
||||
opaque and not user-friendly identifiers). Each User MUST include
|
||||
a non-empty userName value. This identifier MUST be unique across
|
||||
the service provider's entire set of Users. This attribute is
|
||||
REQUIRED and is case insensitive.
|
||||
opaque and not user-friendly identifiers). Each User MUST include a
|
||||
non-empty userName value. This identifier MUST be unique across the
|
||||
service provider's entire set of Users. This attribute is REQUIRED
|
||||
and is case insensitive.
|
||||
"""
|
||||
|
||||
password: Optional[str]
|
||||
|
@ -92,115 +92,103 @@ class User:
|
|||
"""
|
||||
|
||||
preferred_language: Optional[str]
|
||||
"""
|
||||
Indicates the user's preferred written or spoken languages and is
|
||||
generally used for selecting a localized user interface. The
|
||||
value indicates the set of natural languages that are preferred.
|
||||
"""Indicates the user's preferred written or spoken languages and is
|
||||
generally used for selecting a localized user interface.
|
||||
|
||||
The value indicates the set of natural languages that are preferred.
|
||||
The format of the value is the same as the HTTP Accept-Language
|
||||
header field (not including "Accept-Language:") and is specified
|
||||
in Section 5.3.5 of [RFC7231]. The intent of this value is to
|
||||
enable cloud applications to perform matching of language tags
|
||||
[RFC4647] to the user's language preferences, regardless of what
|
||||
may be indicated by a user agent (which might be shared), or in an
|
||||
interaction that does not involve a user (such as in a delegated
|
||||
OAuth 2.0 [RFC6749] style interaction) where normal HTTP
|
||||
Accept-Language header negotiation cannot take place.
|
||||
header field (not including "Accept-Language:") and is specified in
|
||||
Section 5.3.5 of [RFC7231]. The intent of this value is to enable
|
||||
cloud applications to perform matching of language tags [RFC4647] to
|
||||
the user's language preferences, regardless of what may be indicated
|
||||
by a user agent (which might be shared), or in an interaction that
|
||||
does not involve a user (such as in a delegated OAuth 2.0 [RFC6749]
|
||||
style interaction) where normal HTTP Accept-Language header
|
||||
negotiation cannot take place.
|
||||
"""
|
||||
|
||||
family_name: Optional[str]
|
||||
"""
|
||||
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").
|
||||
"""
|
||||
"""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]
|
||||
"""
|
||||
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").
|
||||
"""
|
||||
"""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]
|
||||
"""
|
||||
The full name, including all middle names, titles, and
|
||||
suffixes as appropriate, formatted for display (e.g.,
|
||||
"Ms. Barbara Jane Jensen, III").
|
||||
"""
|
||||
"""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]
|
||||
"""
|
||||
The name of the user, suitable for display to end-users. Each
|
||||
user returned MAY include a non-empty displayName value. The name
|
||||
SHOULD be the full name of the User being described, if known
|
||||
"""The name of the user, suitable for display to end-users.
|
||||
|
||||
Each user returned MAY include a non-empty displayName value. The
|
||||
name SHOULD be the full name of the User being described, if known
|
||||
(e.g., "Babs Jensen" or "Ms. Barbara J Jensen, III") but MAY be a
|
||||
username or handle, if that is all that is available (e.g.,
|
||||
"bjensen"). The value provided SHOULD be the primary textual
|
||||
label by which this User is normally displayed by the service
|
||||
provider when presenting it to end-users.
|
||||
"bjensen"). The value provided SHOULD be the primary textual label
|
||||
by which this User is normally displayed by the service provider
|
||||
when presenting it to end-users.
|
||||
"""
|
||||
|
||||
emails: List[str]
|
||||
"""
|
||||
Email addresses for the User. The value SHOULD be specified
|
||||
according to [RFC5321]. Service providers SHOULD canonicalize the
|
||||
value according to [RFC5321], e.g., "bjensen@example.com" instead
|
||||
of "bjensen@EXAMPLE.COM". The "display" sub-attribute MAY be used
|
||||
to return the canonicalized representation of the email value.
|
||||
The "type" sub-attribute is used to provide a classification
|
||||
meaningful to the (human) user. The user interface should
|
||||
encourage the use of basic values of "work", "home", and "other"
|
||||
and MAY allow additional type values to be used at the discretion
|
||||
of SCIM clients.
|
||||
"""Email addresses for the User.
|
||||
|
||||
The value SHOULD be specified according to [RFC5321]. Service
|
||||
providers SHOULD canonicalize the value according to [RFC5321],
|
||||
e.g., "bjensen@example.com" instead of "bjensen@EXAMPLE.COM". The
|
||||
"display" sub-attribute MAY be used to return the canonicalized
|
||||
representation of the email value. The "type" sub-attribute is used
|
||||
to provide a classification meaningful to the (human) user. The
|
||||
user interface should encourage the use of basic values of "work",
|
||||
"home", and "other" and MAY allow additional type values to be used
|
||||
at the discretion of SCIM clients.
|
||||
"""
|
||||
|
||||
phone_numbers: List[str]
|
||||
"""
|
||||
Phone numbers for the user. The value SHOULD be specified
|
||||
according to the format defined in [RFC3966], e.g.,
|
||||
'tel:+1-201-555-0123'. Service providers SHOULD canonicalize the
|
||||
value according to [RFC3966] format, when appropriate. The
|
||||
"display" sub-attribute MAY be used to return the canonicalized
|
||||
representation of the phone number value. The sub-attribute
|
||||
"type" often has typical values of "work", "home", "mobile",
|
||||
"fax", "pager", and "other" and MAY allow more types to be defined
|
||||
by the SCIM clients.
|
||||
"""Phone numbers for the user.
|
||||
|
||||
The value SHOULD be specified according to the format defined in
|
||||
[RFC3966], e.g., 'tel:+1-201-555-0123'. Service providers SHOULD
|
||||
canonicalize the value according to [RFC3966] format, when
|
||||
appropriate. The "display" sub-attribute MAY be used to return the
|
||||
canonicalized representation of the phone number value. The sub-
|
||||
attribute "type" often has typical values of "work", "home",
|
||||
"mobile", "fax", "pager", and "other" and MAY allow more types to be
|
||||
defined by the SCIM clients.
|
||||
"""
|
||||
|
||||
formatted_address: Optional[str]
|
||||
"""
|
||||
The full mailing address, formatted for display or use
|
||||
with a mailing label. This attribute MAY contain newlines.
|
||||
"""The full mailing address, formatted for display or use with a mailing
|
||||
label.
|
||||
|
||||
This attribute MAY contain newlines.
|
||||
"""
|
||||
|
||||
street: Optional[str]
|
||||
"""
|
||||
The full street address component, which may
|
||||
include house number, street name, P.O. box, and multi-line
|
||||
extended street address information. This attribute MAY
|
||||
contain newlines.
|
||||
"""The full street address component, which may include house number,
|
||||
street name, P.O.
|
||||
|
||||
box, and multi-line extended street address information. This
|
||||
attribute MAY contain newlines.
|
||||
"""
|
||||
|
||||
postal_code: Optional[str]
|
||||
"""
|
||||
The zip code or postal code component.
|
||||
"""
|
||||
"""The zip code or postal code component."""
|
||||
|
||||
locality: Optional[str]
|
||||
"""
|
||||
The city or locality component.
|
||||
"""
|
||||
"""The city or locality component."""
|
||||
|
||||
region: Optional[str]
|
||||
"""
|
||||
The state or region component.
|
||||
"""
|
||||
"""The state or region component."""
|
||||
|
||||
photo: Optional[str]
|
||||
"""
|
||||
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. The resource MUST be a file (e.g.,
|
||||
"""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.
|
||||
|
||||
The resource MUST be a file (e.g.,
|
||||
a GIF, JPEG, or PNG image file) rather than a web page containing
|
||||
an image. Service providers MAY return the same image in
|
||||
different sizes, although it is recognized that no standard for
|
||||
|
@ -214,74 +202,63 @@ class User:
|
|||
"""
|
||||
|
||||
profile_url: Optional[str]
|
||||
"""
|
||||
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). URIs
|
||||
are canonicalized per Section 6.2 of [RFC3986].
|
||||
"""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).
|
||||
|
||||
URIs are canonicalized per Section 6.2 of [RFC3986].
|
||||
"""
|
||||
|
||||
title: Optional[str]
|
||||
"""
|
||||
The user's title, such as "Vice President".
|
||||
"""
|
||||
"""The user's title, such as "Vice President"."""
|
||||
|
||||
organization: Optional[str]
|
||||
"""
|
||||
Identifies the name of an organization.
|
||||
"""
|
||||
"""Identifies the name of an organization."""
|
||||
|
||||
employee_number: Optional[str]
|
||||
"""
|
||||
A string identifier, typically numeric or alphanumeric, assigned
|
||||
to a person, typically based on order of hire or association with
|
||||
an organization.
|
||||
"""
|
||||
"""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]
|
||||
"""
|
||||
Identifies the name of a department.
|
||||
"""
|
||||
"""Identifies the name of a department."""
|
||||
|
||||
last_modified: Optional[datetime.datetime]
|
||||
"""
|
||||
The most recent DateTime that the details of this
|
||||
resource were updated at the service provider. If this
|
||||
resource has never been modified since its initial creation,
|
||||
"""The most recent DateTime that the details of this resource were updated
|
||||
at the service provider.
|
||||
|
||||
If this resource has never been modified since its initial creation,
|
||||
the value MUST be the same as the value of "created".
|
||||
"""
|
||||
|
||||
groups: List["Group"]
|
||||
"""
|
||||
A list of groups to which the user belongs, either through direct
|
||||
membership, through nested groups, or dynamically calculated. The
|
||||
values are meant to enable expression of common group-based or
|
||||
role-based access control models, although no explicit
|
||||
authorization model is defined. It is intended that the semantics
|
||||
of group membership and any behavior or authorization granted as a
|
||||
result of membership are defined by the service provider. The
|
||||
canonical types "direct" and "indirect" are defined to describe
|
||||
how the group membership was derived. Direct group membership
|
||||
indicates that the user is directly associated with the group and
|
||||
SHOULD indicate that clients may modify membership through the
|
||||
"Group" resource. Indirect membership indicates that user
|
||||
membership is transitive or dynamic and implies that clients
|
||||
cannot modify indirect group membership through the "Group"
|
||||
resource but MAY modify direct group membership through the
|
||||
"Group" resource, which may influence indirect memberships. If
|
||||
the SCIM service provider exposes a "Group" resource, the "value"
|
||||
sub-attribute MUST be the "id", and the "$ref" sub-attribute must
|
||||
be the URI of the corresponding "Group" resources to which the
|
||||
user belongs. Since this attribute has a mutability of
|
||||
"readOnly", group membership changes MUST be applied via the
|
||||
"Group" Resource (Section 4.2). This attribute has a mutability
|
||||
of "readOnly".
|
||||
"""A list of groups to which the user belongs, either through direct
|
||||
membership, through nested groups, or dynamically calculated.
|
||||
|
||||
The values are meant to enable expression of common group-based or
|
||||
role-based access control models, although no explicit authorization
|
||||
model is defined. It is intended that the semantics of group
|
||||
membership and any behavior or authorization granted as a result of
|
||||
membership are defined by the service provider. The canonical types
|
||||
"direct" and "indirect" are defined to describe how the group
|
||||
membership was derived. Direct group membership indicates that the
|
||||
user is directly associated with the group and SHOULD indicate that
|
||||
clients may modify membership through the "Group" resource. Indirect
|
||||
membership indicates that user membership is transitive or dynamic
|
||||
and implies that clients cannot modify indirect group membership
|
||||
through the "Group" resource but MAY modify direct group membership
|
||||
through the "Group" resource, which may influence indirect
|
||||
memberships. If the SCIM service provider exposes a "Group"
|
||||
resource, the "value" sub-attribute MUST be the "id", and the "$ref"
|
||||
sub-attribute must be the URI of the corresponding "Group" resources
|
||||
to which the user belongs. Since this attribute has a mutability of
|
||||
"readOnly", group membership changes MUST be applied via the "Group"
|
||||
Resource (Section 4.2). This attribute has a mutability of
|
||||
"readOnly".
|
||||
"""
|
||||
|
||||
lock_date: Optional[datetime.datetime]
|
||||
"""
|
||||
A DateTime indicating when the resource was locked.
|
||||
"""
|
||||
"""A DateTime indicating when the resource was locked."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.read = set()
|
||||
|
@ -294,21 +271,15 @@ class User:
|
|||
raise NotImplementedError()
|
||||
|
||||
def has_password(self) -> bool:
|
||||
"""
|
||||
Checks wether a password has been set for the user.
|
||||
"""
|
||||
"""Checks wether a password has been set for the user."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def check_password(self, password: str) -> bool:
|
||||
"""
|
||||
Checks if the password matches the user password in the database.
|
||||
"""
|
||||
"""Checks if the password matches the user password in the database."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_password(self, password: str):
|
||||
"""
|
||||
Sets a password for the user.
|
||||
"""
|
||||
"""Sets a password for the user."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def can_read(self, field: str):
|
||||
|
@ -327,9 +298,7 @@ class User:
|
|||
|
||||
@property
|
||||
def locked(self) -> bool:
|
||||
"""
|
||||
Wether the user account has been locked or has expired.
|
||||
"""
|
||||
"""Wether the user account has been locked or has expired."""
|
||||
return bool(self.lock_date) and self.lock_date < datetime.datetime.now(
|
||||
datetime.timezone.utc
|
||||
)
|
||||
|
@ -342,39 +311,40 @@ class Group:
|
|||
"""
|
||||
|
||||
id: str
|
||||
"""
|
||||
A unique identifier for a SCIM resource as defined by the service
|
||||
provider. Each representation of the resource MUST include a
|
||||
non-empty "id" value. This identifier MUST be unique across the
|
||||
SCIM service provider's entire set of resources. It MUST be a
|
||||
stable, non-reassignable identifier that does not change when the
|
||||
same resource is returned in subsequent requests. The value of
|
||||
the "id" attribute is always issued by the service provider and
|
||||
MUST NOT be specified by the client. The string "bulkId" is a
|
||||
reserved keyword and MUST NOT be used within any unique identifier
|
||||
value. The attribute characteristics are "caseExact" as "true", a
|
||||
mutability of "readOnly", and a "returned" characteristic of
|
||||
"always". See Section 9 for additional considerations regarding
|
||||
privacy.
|
||||
"""A unique identifier for a SCIM resource as defined by the service
|
||||
provider.
|
||||
|
||||
Each representation of the resource MUST include a non-empty "id"
|
||||
value. This identifier MUST be unique across the SCIM service
|
||||
provider's entire set of resources. It MUST be a stable, non-
|
||||
reassignable identifier that does not change when the same resource
|
||||
is returned in subsequent requests. The value of the "id" attribute
|
||||
is always issued by the service provider and MUST NOT be specified
|
||||
by the client. The string "bulkId" is a reserved keyword and MUST
|
||||
NOT be used within any unique identifier value. The attribute
|
||||
characteristics are "caseExact" as "true", a mutability of
|
||||
"readOnly", and a "returned" characteristic of "always". See
|
||||
Section 9 for additional considerations regarding privacy.
|
||||
"""
|
||||
|
||||
display_name: str
|
||||
"""
|
||||
A human-readable name for the Group. REQUIRED.
|
||||
"""A human-readable name for the Group.
|
||||
|
||||
REQUIRED.
|
||||
"""
|
||||
|
||||
members: List["User"]
|
||||
"""
|
||||
A list of members of the Group. While values MAY be added or
|
||||
removed, sub-attributes of members are "immutable". The "value"
|
||||
sub-attribute contains the value of an "id" attribute of a SCIM
|
||||
resource, and the "$ref" sub-attribute must be the URI of a SCIM
|
||||
resource such as a "User", or a "Group". The intention of the
|
||||
"Group" type is to allow the service provider to support nested
|
||||
groups. Service providers MAY require clients to provide a
|
||||
non-empty value by setting the "required" attribute characteristic
|
||||
of a sub-attribute of the "members" attribute in the "Group"
|
||||
resource schema.
|
||||
"""A list of members of the Group.
|
||||
|
||||
While values MAY be added or removed, sub-attributes of members are
|
||||
"immutable". The "value" sub-attribute contains the value of an
|
||||
"id" attribute of a SCIM resource, and the "$ref" sub-attribute must
|
||||
be the URI of a SCIM resource such as a "User", or a "Group". The
|
||||
intention of the "Group" type is to allow the service provider to
|
||||
support nested groups. Service providers MAY require clients to
|
||||
provide a non-empty value by setting the "required" attribute
|
||||
characteristic of a sub-attribute of the "members" attribute in the
|
||||
"Group" resource schema.
|
||||
"""
|
||||
|
||||
description: Optional[str]
|
||||
|
|
|
@ -22,53 +22,54 @@ class Client(Model):
|
|||
audience: List["Client"]
|
||||
|
||||
client_id: Optional[str]
|
||||
"""
|
||||
REQUIRED. OAuth 2.0 client identifier string. It SHOULD NOT be
|
||||
currently valid for any other registered client, though an
|
||||
authorization server MAY issue the same client identifier to
|
||||
multiple instances of a registered client at its discretion.
|
||||
"""REQUIRED.
|
||||
|
||||
OAuth 2.0 client identifier string. It SHOULD NOT be currently
|
||||
valid for any other registered client, though an authorization
|
||||
server MAY issue the same client identifier to multiple instances of
|
||||
a registered client at its discretion.
|
||||
"""
|
||||
|
||||
client_secret: Optional[str]
|
||||
"""
|
||||
OPTIONAL. OAuth 2.0 client secret string. If issued, this MUST
|
||||
be unique for each "client_id" and SHOULD be unique for multiple
|
||||
instances of a client using the same "client_id". This value is
|
||||
used by confidential clients to authenticate to the token
|
||||
endpoint, as described in OAuth 2.0 [RFC6749], Section 2.3.1.
|
||||
"""OPTIONAL.
|
||||
|
||||
OAuth 2.0 client secret string. If issued, this MUST be unique for
|
||||
each "client_id" and SHOULD be unique for multiple instances of a
|
||||
client using the same "client_id". This value is used by
|
||||
confidential clients to authenticate to the token endpoint, as
|
||||
described in OAuth 2.0 [RFC6749], Section 2.3.1.
|
||||
"""
|
||||
|
||||
client_id_issued_at: Optional[datetime.datetime]
|
||||
"""
|
||||
OPTIONAL. Time at which the client identifier was issued. The
|
||||
time is represented as the number of seconds from
|
||||
1970-01-01T00:00:00Z as measured in UTC until the date/time of
|
||||
issuance.
|
||||
"""OPTIONAL.
|
||||
|
||||
Time at which the client identifier was issued. The time is
|
||||
represented as the number of seconds from 1970-01-01T00:00:00Z as
|
||||
measured in UTC until the date/time of issuance.
|
||||
"""
|
||||
|
||||
client_secret_expires_at: Optional[datetime.datetime]
|
||||
"""
|
||||
REQUIRED if "client_secret" is issued. Time at which the client
|
||||
secret will expire or 0 if it will not expire. The time is
|
||||
represented as the number of seconds from 1970-01-01T00:00:00Z as
|
||||
measured in UTC until the date/time of expiration.
|
||||
"""REQUIRED if "client_secret" is issued.
|
||||
|
||||
Time at which the client secret will expire or 0 if it will not
|
||||
expire. The time is represented as the number of seconds from
|
||||
1970-01-01T00:00:00Z as measured in UTC until the date/time of
|
||||
expiration.
|
||||
"""
|
||||
|
||||
redirect_uris: List[str]
|
||||
"""
|
||||
Array of redirection URI strings for use in redirect-based flows
|
||||
such as the authorization code and implicit flows. As required by
|
||||
Section 2 of OAuth 2.0 [RFC6749], clients using flows with
|
||||
redirection MUST register their redirection URI values.
|
||||
"""Array of redirection URI strings for use in redirect-based flows such as
|
||||
the authorization code and implicit flows.
|
||||
|
||||
As required by Section 2 of OAuth 2.0 [RFC6749], clients using flows
|
||||
with redirection MUST register their redirection URI values.
|
||||
Authorization servers that support dynamic registration for
|
||||
redirect-based flows MUST implement support for this metadata
|
||||
value.
|
||||
redirect-based flows MUST implement support for this metadata value.
|
||||
"""
|
||||
|
||||
token_endpoint_auth_method: Optional[str]
|
||||
"""
|
||||
String indicator of the requested authentication method for the
|
||||
token endpoint. Values defined by this specification are:
|
||||
"""String indicator of the requested authentication method for the token
|
||||
endpoint. Values defined by this specification are:
|
||||
|
||||
* "none": The client is a public client as defined in OAuth 2.0,
|
||||
Section 2.1, and does not have a client secret.
|
||||
|
@ -88,9 +89,8 @@ class Client(Model):
|
|||
"""
|
||||
|
||||
grant_types: List[str]
|
||||
"""
|
||||
Array of OAuth 2.0 grant type strings that the client can use at
|
||||
the token endpoint. These grant types are defined as follows:
|
||||
"""Array of OAuth 2.0 grant type strings that the client can use at the
|
||||
token endpoint. These grant types are defined as follows:
|
||||
|
||||
* "authorization_code": The authorization code grant type defined
|
||||
in OAuth 2.0, Section 4.1.
|
||||
|
@ -146,134 +146,137 @@ class Client(Model):
|
|||
"""
|
||||
|
||||
client_name: Optional[str]
|
||||
"""
|
||||
Human-readable string name of the client to be presented to the
|
||||
end-user during authorization. If omitted, the authorization
|
||||
server MAY display the raw "client_id" value to the end-user
|
||||
instead. It is RECOMMENDED that clients always send this field.
|
||||
The value of this field MAY be internationalized, as described in
|
||||
Section 2.2.
|
||||
"""Human-readable string name of the client to be presented to the end-user
|
||||
during authorization.
|
||||
|
||||
If omitted, the authorization server MAY display the raw "client_id"
|
||||
value to the end-user instead. It is RECOMMENDED that clients
|
||||
always send this field. The value of this field MAY be
|
||||
internationalized, as described in Section 2.2.
|
||||
"""
|
||||
|
||||
client_uri: Optional[str]
|
||||
"""
|
||||
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 clickable fashion. It is RECOMMENDED that clients always send
|
||||
this field. The value of this field MUST point to a valid web
|
||||
page. The value of this field MAY be internationalized, as
|
||||
described in Section 2.2.
|
||||
"""
|
||||
"""URL string of a web page providing information about the client.
|
||||
|
||||
logo_uri: Optional[str]
|
||||
"""
|
||||
URL string that references a logo for the client. If present, the
|
||||
server SHOULD display this image to the end-user during approval.
|
||||
The value of this field MUST point to a valid image file. The
|
||||
If present, the server SHOULD display this URL to the end-user in a
|
||||
clickable fashion. It is RECOMMENDED that clients always send this
|
||||
field. The value of this field MUST point to a valid web page. The
|
||||
value of this field MAY be internationalized, as described in
|
||||
Section 2.2.
|
||||
"""
|
||||
|
||||
scope: List[str]
|
||||
logo_uri: Optional[str]
|
||||
"""URL string that references a logo for the client.
|
||||
|
||||
If present, the server SHOULD display this image to the end-user
|
||||
during approval. The value of this field MUST point to a valid image
|
||||
file. The value of this field MAY be internationalized, as
|
||||
described in Section 2.2.
|
||||
"""
|
||||
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. The semantics of values in
|
||||
this list are service specific. If omitted, an authorization
|
||||
server MAY register a client with a default set of scopes.
|
||||
|
||||
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.
|
||||
|
||||
The semantics of values in this list are service specific. If
|
||||
omitted, an authorization server MAY register a client with a
|
||||
default set of scopes.
|
||||
"""
|
||||
|
||||
contacts: List[str]
|
||||
"""
|
||||
Array of strings representing ways to contact people responsible
|
||||
for this client, typically email addresses. The authorization
|
||||
server MAY make these contact addresses available to end-users for
|
||||
support requests for the client. See Section 6 for information on
|
||||
Privacy Considerations.
|
||||
"""Array of strings representing ways to contact people responsible for
|
||||
this client, typically email addresses.
|
||||
|
||||
The authorization server MAY make these contact addresses available
|
||||
to end-users for support requests for the client. See Section 6 for
|
||||
information on Privacy Considerations.
|
||||
"""
|
||||
|
||||
tos_uri: Optional[str]
|
||||
"""
|
||||
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. The authorization server SHOULD display
|
||||
this URL to the end-user if it is provided. The value of this
|
||||
field MUST point to a valid web page. The value of this field MAY
|
||||
be internationalized, as described in Section 2.2.
|
||||
"""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.
|
||||
|
||||
The authorization server SHOULD display this URL to the end-user if
|
||||
it is provided. The value of this field MUST point to a valid web
|
||||
page. The value of this field MAY be internationalized, as
|
||||
described in Section 2.2.
|
||||
"""
|
||||
|
||||
policy_uri: Optional[str]
|
||||
"""
|
||||
URL string that points to a human-readable privacy policy document
|
||||
that describes how the deployment organization collects, uses,
|
||||
retains, and discloses personal data. The authorization server
|
||||
SHOULD display this URL to the end-user if it is provided. The
|
||||
value of this field MUST point to a valid web page. The value of
|
||||
this field MAY be internationalized, as described in Section 2.2.
|
||||
"""URL string that points to a human-readable privacy policy document that
|
||||
describes how the deployment organization collects, uses, retains, and
|
||||
discloses personal data.
|
||||
|
||||
The authorization server SHOULD display this URL to the end-user if
|
||||
it is provided. The value of this field MUST point to a valid web
|
||||
page. The value of this field MAY be internationalized, as
|
||||
described in Section 2.2.
|
||||
"""
|
||||
|
||||
jwks_uri: Optional[str]
|
||||
"""
|
||||
URL string referencing the client's JSON Web Key (JWK) Set
|
||||
[RFC7517] document, which contains the client's public keys. The
|
||||
value of this field MUST point to a valid JWK Set document. These
|
||||
keys can be used by higher-level protocols that use signing or
|
||||
"""URL string referencing the client's JSON Web Key (JWK) Set [RFC7517]
|
||||
document, which contains the client's public keys.
|
||||
|
||||
The value of this field MUST point to a valid JWK Set document.
|
||||
These keys can be used by higher-level protocols that use signing or
|
||||
encryption. For instance, these keys might be used by some
|
||||
applications for validating signed requests made to the token
|
||||
endpoint when using JWTs for client authentication [RFC7523]. Use
|
||||
of this parameter is preferred over the "jwks" parameter, as it
|
||||
allows for easier key rotation. The "jwks_uri" and "jwks"
|
||||
parameters MUST NOT both be present in the same request or
|
||||
response.
|
||||
parameters MUST NOT both be present in the same request or response.
|
||||
"""
|
||||
|
||||
jwk: Optional[str]
|
||||
"""
|
||||
Client's JSON Web Key Set [RFC7517] document value, which contains
|
||||
the client's public keys. The value of this field MUST be a JSON
|
||||
object containing a valid JWK Set. These keys can be used by
|
||||
higher-level protocols that use signing or encryption. This
|
||||
parameter is intended to be used by clients that cannot use the
|
||||
"jwks_uri" parameter, such as native clients that cannot host
|
||||
public URLs. The "jwks_uri" and "jwks" parameters MUST NOT both
|
||||
be present in the same request or response.
|
||||
"""Client's JSON Web Key Set [RFC7517] document value, which contains the
|
||||
client's public keys.
|
||||
|
||||
The value of this field MUST be a JSON object containing a valid JWK
|
||||
Set. These keys can be used by higher-level protocols that use
|
||||
signing or encryption. This parameter is intended to be used by
|
||||
clients that cannot use the "jwks_uri" parameter, such as native
|
||||
clients that cannot host public URLs. The "jwks_uri" and "jwks"
|
||||
parameters MUST NOT both be present in the same request or response.
|
||||
"""
|
||||
|
||||
software_id: Optional[str]
|
||||
"""
|
||||
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 registered. Unlike "client_id", which is issued by
|
||||
the authorization server and SHOULD vary between instances, the
|
||||
"software_id" SHOULD remain the same for all instances of the
|
||||
client software. The "software_id" SHOULD remain the same across
|
||||
multiple updates or versions of the same piece of software. The
|
||||
value of this field is not intended to be human readable and is
|
||||
usually opaque to the client and authorization server.
|
||||
"""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
|
||||
registered.
|
||||
|
||||
Unlike "client_id", which is issued by the authorization server and
|
||||
SHOULD vary between instances, the "software_id" SHOULD remain the
|
||||
same for all instances of the client software. The "software_id"
|
||||
SHOULD remain the same across multiple updates or versions of the
|
||||
same piece of software. The value of this field is not intended to
|
||||
be human readable and is usually opaque to the client and
|
||||
authorization server.
|
||||
"""
|
||||
|
||||
software_version: Optional[str]
|
||||
"""
|
||||
A version identifier string for the client software identified by
|
||||
"software_id". The value of the "software_version" SHOULD change
|
||||
on any update to the client software identified by the same
|
||||
"software_id". The value of this field is intended to be compared
|
||||
using string equality matching and no other comparison semantics
|
||||
are defined by this specification. The value of this field is
|
||||
outside the scope of this specification, but it is not intended to
|
||||
be human readable and is usually opaque to the client and
|
||||
authorization server. The definition of what constitutes an
|
||||
update to client software that would trigger a change to this
|
||||
value is specific to the software itself and is outside the scope
|
||||
of this specification.
|
||||
"""A version identifier string for the client software identified by
|
||||
"software_id".
|
||||
|
||||
The value of the "software_version" SHOULD change on any update to
|
||||
the client software identified by the same "software_id". The value
|
||||
of this field is intended to be compared using string equality
|
||||
matching and no other comparison semantics are defined by this
|
||||
specification. The value of this field is outside the scope of this
|
||||
specification, but it is not intended to be human readable and is
|
||||
usually opaque to the client and authorization server. The
|
||||
definition of what constitutes an update to client software that
|
||||
would trigger a change to this value is specific to the software
|
||||
itself and is outside the scope of this specification.
|
||||
"""
|
||||
|
||||
post_logout_redirect_uris: List[str]
|
||||
"""
|
||||
OPTIONAL. Array of URLs supplied by the RP to which it MAY request
|
||||
that the End-User's User Agent be redirected using the
|
||||
"""OPTIONAL.
|
||||
|
||||
Array of URLs supplied by the RP to which it MAY request that the
|
||||
End-User's User Agent be redirected using the
|
||||
post_logout_redirect_uri parameter after a logout has been
|
||||
performed. These URLs SHOULD use the https scheme and MAY contain
|
||||
port, path, and query parameter components; however, they MAY use
|
||||
|
@ -284,9 +287,7 @@ class Client(Model):
|
|||
|
||||
|
||||
class AuthorizationCode(Model):
|
||||
"""
|
||||
OpenID Connect temporary authorization code definition.
|
||||
"""
|
||||
"""OpenID Connect temporary authorization code definition."""
|
||||
|
||||
id: str
|
||||
authorization_code_id: str
|
||||
|
@ -305,9 +306,7 @@ class AuthorizationCode(Model):
|
|||
|
||||
|
||||
class Token(Model):
|
||||
"""
|
||||
OpenID Connect token definition.
|
||||
"""
|
||||
"""OpenID Connect token definition."""
|
||||
|
||||
id: str
|
||||
token_id: str
|
||||
|
@ -324,9 +323,7 @@ class Token(Model):
|
|||
|
||||
|
||||
class Consent(Model):
|
||||
"""
|
||||
Long-term user consent to an application.
|
||||
"""
|
||||
"""Long-term user consent to an application."""
|
||||
|
||||
id: str
|
||||
consent_id: str
|
||||
|
|
|
@ -8,9 +8,7 @@ from flask.cli import with_appcontext
|
|||
@with_appcontext
|
||||
@with_backendcontext
|
||||
def clean():
|
||||
"""
|
||||
Remove expired tokens and authorization codes.
|
||||
"""
|
||||
"""Remove expired tokens and authorization codes."""
|
||||
for t in models.Token.query():
|
||||
if t.is_expired():
|
||||
t.delete()
|
||||
|
|
|
@ -8,10 +8,8 @@ from flask import url_for
|
|||
|
||||
|
||||
def test_confirmation_disabled_email_editable(testclient, backend, logged_user):
|
||||
"""
|
||||
If email confirmation is disabled, users should be able to pick
|
||||
any email.
|
||||
"""
|
||||
"""If email confirmation is disabled, users should be able to pick any
|
||||
email."""
|
||||
testclient.app.config["EMAIL_CONFIRMATION"] = False
|
||||
|
||||
res = testclient.get("/profile/user")
|
||||
|
@ -34,11 +32,9 @@ def test_confirmation_disabled_email_editable(testclient, backend, logged_user):
|
|||
def test_confirmation_unset_smtp_disabled_email_editable(
|
||||
testclient, backend, logged_admin, user
|
||||
):
|
||||
"""
|
||||
If email confirmation is unset and no SMTP server has
|
||||
been configured, then email confirmation cannot be enabled,
|
||||
thus users must be able to pick any email.
|
||||
"""
|
||||
"""If email confirmation is unset and no SMTP server has been configured,
|
||||
then email confirmation cannot be enabled, thus users must be able to pick
|
||||
any email."""
|
||||
del testclient.app.config["SMTP"]
|
||||
testclient.app.config["EMAIL_CONFIRMATION"] = None
|
||||
|
||||
|
@ -59,9 +55,9 @@ def test_confirmation_unset_smtp_disabled_email_editable(
|
|||
|
||||
|
||||
def test_confirmation_enabled_smtp_disabled_readonly(testclient, backend, logged_user):
|
||||
"""
|
||||
If email confirmation is enabled and no SMTP server is configured,
|
||||
this might be a misconfiguration, or a temporary SMTP disabling.
|
||||
"""If email confirmation is enabled and no SMTP server is configured, this
|
||||
might be a misconfiguration, or a temporary SMTP disabling.
|
||||
|
||||
In doubt, users cannot edit their emails.
|
||||
"""
|
||||
del testclient.app.config["SMTP"]
|
||||
|
@ -78,10 +74,8 @@ def test_confirmation_enabled_smtp_disabled_readonly(testclient, backend, logged
|
|||
def test_confirmation_unset_smtp_enabled_email_admin_editable(
|
||||
testclient, backend, logged_admin, user
|
||||
):
|
||||
"""
|
||||
Administrators should be able to edit user email addresses,
|
||||
even when email confirmation is unset and SMTP is configured.
|
||||
"""
|
||||
"""Administrators should be able to edit user email addresses, even when
|
||||
email confirmation is unset and SMTP is configured."""
|
||||
testclient.app.config["EMAIL_CONFIRMATION"] = None
|
||||
|
||||
res = testclient.get("/profile/user")
|
||||
|
@ -103,10 +97,8 @@ def test_confirmation_unset_smtp_enabled_email_admin_editable(
|
|||
def test_confirmation_enabled_smtp_disabled_admin_editable(
|
||||
testclient, backend, logged_admin, user
|
||||
):
|
||||
"""
|
||||
Administrators should be able to edit user email addresses,
|
||||
even when email confirmation is enabled and SMTP is disabled.
|
||||
"""
|
||||
"""Administrators should be able to edit user email addresses, even when
|
||||
email confirmation is enabled and SMTP is disabled."""
|
||||
testclient.app.config["EMAIL_CONFIRMATION"] = True
|
||||
del testclient.app.config["SMTP"]
|
||||
|
||||
|
@ -129,11 +121,8 @@ def test_confirmation_enabled_smtp_disabled_admin_editable(
|
|||
def test_confirmation_unset_smtp_enabled_email_user_validation(
|
||||
smtpd, testclient, backend, user
|
||||
):
|
||||
"""
|
||||
If email confirmation is unset and there is a SMTP server
|
||||
configured, then users emails should be validated by sending
|
||||
a confirmation email.
|
||||
"""
|
||||
"""If email confirmation is unset and there is a SMTP server configured,
|
||||
then users emails should be validated by sending a confirmation email."""
|
||||
testclient.app.config["EMAIL_CONFIRMATION"] = None
|
||||
|
||||
with freezegun.freeze_time("2020-01-01 01:00:00"):
|
||||
|
@ -188,9 +177,7 @@ def test_confirmation_unset_smtp_enabled_email_user_validation(
|
|||
|
||||
|
||||
def test_confirmation_invalid_link(testclient, backend, user):
|
||||
"""
|
||||
Random confirmation links should fail.
|
||||
"""
|
||||
"""Random confirmation links should fail."""
|
||||
res = testclient.get("/email-confirmation/invalid/invalid")
|
||||
assert (
|
||||
"error",
|
||||
|
@ -199,9 +186,7 @@ def test_confirmation_invalid_link(testclient, backend, user):
|
|||
|
||||
|
||||
def test_confirmation_mail_form_failed(testclient, backend, user):
|
||||
"""
|
||||
Tests when an error happens during the mail sending.
|
||||
"""
|
||||
"""Tests when an error happens during the mail sending."""
|
||||
with freezegun.freeze_time("2020-01-01 01:00:00"):
|
||||
res = testclient.get("/login")
|
||||
res.form["login"] = "user"
|
||||
|
@ -227,9 +212,7 @@ def test_confirmation_mail_form_failed(testclient, backend, user):
|
|||
|
||||
@mock.patch("smtplib.SMTP")
|
||||
def test_confirmation_mail_send_failed(SMTP, smtpd, testclient, backend, user):
|
||||
"""
|
||||
Tests when an error happens during the mail sending.
|
||||
"""
|
||||
"""Tests when an error happens during the mail sending."""
|
||||
SMTP.side_effect = mock.Mock(side_effect=OSError("unit test mail error"))
|
||||
with freezegun.freeze_time("2020-01-01 01:00:00"):
|
||||
res = testclient.get("/login")
|
||||
|
@ -255,9 +238,7 @@ def test_confirmation_mail_send_failed(SMTP, smtpd, testclient, backend, user):
|
|||
|
||||
|
||||
def test_confirmation_expired_link(testclient, backend, user):
|
||||
"""
|
||||
Expired valid confirmation links should fail.
|
||||
"""
|
||||
"""Expired valid confirmation links should fail."""
|
||||
email_confirmation = EmailConfirmationPayload(
|
||||
"2020-01-01T01:00:00+00:00",
|
||||
"user",
|
||||
|
@ -282,9 +263,7 @@ def test_confirmation_expired_link(testclient, backend, user):
|
|||
|
||||
|
||||
def test_confirmation_invalid_hash_link(testclient, backend, user):
|
||||
"""
|
||||
Confirmation link with invalid hashes should fail.
|
||||
"""
|
||||
"""Confirmation link with invalid hashes should fail."""
|
||||
email_confirmation = EmailConfirmationPayload(
|
||||
"2020-01-01T01:00:00+00:00",
|
||||
"user",
|
||||
|
@ -309,10 +288,10 @@ def test_confirmation_invalid_hash_link(testclient, backend, user):
|
|||
|
||||
|
||||
def test_confirmation_invalid_user_link(testclient, backend, user):
|
||||
"""
|
||||
Confirmation link about an unexisting user should fail.
|
||||
For instance, when the user account has been deleted between
|
||||
the mail is sent and the link is clicked.
|
||||
"""Confirmation link about an unexisting user should fail.
|
||||
|
||||
For instance, when the user account has been deleted between the
|
||||
mail is sent and the link is clicked.
|
||||
"""
|
||||
email_confirmation = EmailConfirmationPayload(
|
||||
"2020-01-01T01:00:00+00:00",
|
||||
|
@ -338,9 +317,7 @@ def test_confirmation_invalid_user_link(testclient, backend, user):
|
|||
|
||||
|
||||
def test_confirmation_email_already_confirmed_link(testclient, backend, user, admin):
|
||||
"""
|
||||
Clicking twice on a confirmation link should fail.
|
||||
"""
|
||||
"""Clicking twice on a confirmation link should fail."""
|
||||
email_confirmation = EmailConfirmationPayload(
|
||||
"2020-01-01T01:00:00+00:00",
|
||||
"user",
|
||||
|
@ -365,10 +342,11 @@ def test_confirmation_email_already_confirmed_link(testclient, backend, user, ad
|
|||
|
||||
|
||||
def test_confirmation_email_already_used_link(testclient, backend, user, admin):
|
||||
"""
|
||||
Confirmation link should fail if the target email is already associated
|
||||
to another account. For instance, if an administrator already put
|
||||
this email to someone else's profile.
|
||||
"""Confirmation link should fail if the target email is already associated
|
||||
to another account.
|
||||
|
||||
For instance, if an administrator already put this email to someone
|
||||
else's profile.
|
||||
"""
|
||||
email_confirmation = EmailConfirmationPayload(
|
||||
"2020-01-01T01:00:00+00:00",
|
||||
|
@ -394,10 +372,8 @@ def test_confirmation_email_already_used_link(testclient, backend, user, admin):
|
|||
|
||||
|
||||
def test_delete_email(testclient, logged_user):
|
||||
"""
|
||||
Tests that user can deletes its emails unless they have only
|
||||
one left.
|
||||
"""
|
||||
"""Tests that user can deletes its emails unless they have only one
|
||||
left."""
|
||||
res = testclient.get("/profile/user")
|
||||
assert "email_remove" not in res.forms["emailconfirmationform"].fields
|
||||
|
||||
|
@ -416,10 +392,7 @@ def test_delete_email(testclient, logged_user):
|
|||
|
||||
|
||||
def test_delete_wrong_email(testclient, logged_user):
|
||||
"""
|
||||
Tests that removing an already removed email do not
|
||||
produce anything.
|
||||
"""
|
||||
"""Tests that removing an already removed email do not produce anything."""
|
||||
logged_user.emails = logged_user.emails + ["new@email.com"]
|
||||
logged_user.save()
|
||||
|
||||
|
@ -440,9 +413,7 @@ def test_delete_wrong_email(testclient, logged_user):
|
|||
|
||||
|
||||
def test_delete_last_email(testclient, logged_user):
|
||||
"""
|
||||
Tests that users cannot remove their last email address.
|
||||
"""
|
||||
"""Tests that users cannot remove their last email address."""
|
||||
logged_user.emails = logged_user.emails + ["new@email.com"]
|
||||
logged_user.save()
|
||||
|
||||
|
@ -463,10 +434,8 @@ def test_delete_last_email(testclient, logged_user):
|
|||
|
||||
|
||||
def test_edition_forced_mail(testclient, logged_user):
|
||||
"""
|
||||
Tests that users that must perform email verification
|
||||
cannot force the profile form.
|
||||
"""
|
||||
"""Tests that users that must perform email verification cannot force the
|
||||
profile form."""
|
||||
res = testclient.get("/profile/user", status=200)
|
||||
form = res.forms["baseform"]
|
||||
testclient.post(
|
||||
|
@ -483,10 +452,8 @@ def test_edition_forced_mail(testclient, logged_user):
|
|||
|
||||
|
||||
def test_invitation_form_mail_field_readonly(testclient):
|
||||
"""
|
||||
Tests that the email field is readonly in the invitation
|
||||
form creation if email confirmation is enabled.
|
||||
"""
|
||||
"""Tests that the email field is readonly in the invitation form creation
|
||||
if email confirmation is enabled."""
|
||||
testclient.app.config["EMAIL_CONFIRMATION"] = True
|
||||
|
||||
payload = RegistrationPayload(
|
||||
|
@ -504,10 +471,8 @@ def test_invitation_form_mail_field_readonly(testclient):
|
|||
|
||||
|
||||
def test_invitation_form_mail_field_writable(testclient):
|
||||
"""
|
||||
Tests that the email field is writable in the invitation
|
||||
form creation if email confirmation is disabled.
|
||||
"""
|
||||
"""Tests that the email field is writable in the invitation form creation
|
||||
if email confirmation is disabled."""
|
||||
testclient.app.config["EMAIL_CONFIRMATION"] = False
|
||||
|
||||
payload = RegistrationPayload(
|
||||
|
|
|
@ -7,9 +7,7 @@ from flask import url_for
|
|||
|
||||
|
||||
def test_registration_without_email_validation(testclient, backend, foo_group):
|
||||
"""
|
||||
Tests a nominal registration without email validation.
|
||||
"""
|
||||
"""Tests a nominal registration without email validation."""
|
||||
testclient.app.config["ENABLE_REGISTRATION"] = True
|
||||
testclient.app.config["EMAIL_CONFIRMATION"] = False
|
||||
|
||||
|
@ -29,9 +27,7 @@ def test_registration_without_email_validation(testclient, backend, foo_group):
|
|||
|
||||
|
||||
def test_registration_with_email_validation(testclient, backend, smtpd, foo_group):
|
||||
"""
|
||||
Tests a nominal registration with email validation.
|
||||
"""
|
||||
"""Tests a nominal registration with email validation."""
|
||||
testclient.app.config["ENABLE_REGISTRATION"] = True
|
||||
|
||||
with freezegun.freeze_time("2020-01-01 02:00:00"):
|
||||
|
@ -84,9 +80,7 @@ def test_registration_with_email_validation(testclient, backend, smtpd, foo_grou
|
|||
def test_registration_with_email_already_taken(
|
||||
testclient, backend, smtpd, user, foo_group
|
||||
):
|
||||
"""
|
||||
Be sure to not leak email existence if HIDE_INVALID_LOGINS is true.
|
||||
"""
|
||||
"""Be sure to not leak email existence if HIDE_INVALID_LOGINS is true."""
|
||||
testclient.app.config["ENABLE_REGISTRATION"] = True
|
||||
|
||||
testclient.app.config["HIDE_INVALID_LOGINS"] = True
|
||||
|
@ -111,26 +105,20 @@ def test_registration_with_email_already_taken(
|
|||
def test_registration_with_email_validation_needs_a_valid_link(
|
||||
testclient, backend, smtpd, foo_group
|
||||
):
|
||||
"""
|
||||
Tests a nominal registration without email validation.
|
||||
"""
|
||||
"""Tests a nominal registration without email validation."""
|
||||
testclient.app.config["ENABLE_REGISTRATION"] = True
|
||||
testclient.get(url_for("core.account.registration"), status=403)
|
||||
|
||||
|
||||
def test_join_page_registration_disabled(testclient, backend, smtpd, foo_group):
|
||||
"""
|
||||
The join page should not be available if registration is disabled.
|
||||
"""
|
||||
"""The join page should not be available if registration is disabled."""
|
||||
testclient.app.config["ENABLE_REGISTRATION"] = False
|
||||
testclient.get(url_for("core.account.join"), status=404)
|
||||
|
||||
|
||||
def test_join_page_email_confirmation_disabled(testclient, backend, smtpd, foo_group):
|
||||
"""
|
||||
The join page should directly redirect to the registration page if
|
||||
email confirmation is disabled.
|
||||
"""
|
||||
"""The join page should directly redirect to the registration page if email
|
||||
confirmation is disabled."""
|
||||
testclient.app.config["ENABLE_REGISTRATION"] = True
|
||||
testclient.app.config["EMAIL_CONFIRMATION"] = False
|
||||
res = testclient.get(url_for("core.account.join"), status=302)
|
||||
|
@ -138,18 +126,14 @@ def test_join_page_email_confirmation_disabled(testclient, backend, smtpd, foo_g
|
|||
|
||||
|
||||
def test_join_page_already_logged_in(testclient, backend, logged_user, foo_group):
|
||||
"""
|
||||
The join page should not be accessible for logged users.
|
||||
"""
|
||||
"""The join page should not be accessible for logged users."""
|
||||
testclient.app.config["ENABLE_REGISTRATION"] = True
|
||||
testclient.get(url_for("core.account.join"), status=403)
|
||||
|
||||
|
||||
@mock.patch("smtplib.SMTP")
|
||||
def test_registration_mail_error(SMTP, testclient, backend, smtpd, foo_group):
|
||||
"""
|
||||
Display an error message if the registration mail could not be sent.
|
||||
"""
|
||||
"""Display an error message if the registration mail could not be sent."""
|
||||
testclient.app.config["ENABLE_REGISTRATION"] = True
|
||||
SMTP.side_effect = mock.Mock(side_effect=OSError("unit test mail error"))
|
||||
res = testclient.get(url_for("core.account.join"))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Tests the behavior of Canaille depending on the OIDC 'prompt' parameter.
|
||||
"""Tests the behavior of Canaille depending on the OIDC 'prompt' parameter.
|
||||
|
||||
https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
|
||||
"""
|
||||
import datetime
|
||||
|
@ -13,9 +13,7 @@ from flask import url_for
|
|||
|
||||
|
||||
def test_prompt_none(testclient, logged_user, client):
|
||||
"""
|
||||
Nominal case with prompt=none
|
||||
"""
|
||||
"""Nominal case with prompt=none."""
|
||||
consent = models.Consent(
|
||||
consent_id=str(uuid.uuid4()),
|
||||
client=client,
|
||||
|
@ -43,16 +41,14 @@ def test_prompt_none(testclient, logged_user, client):
|
|||
|
||||
|
||||
def test_prompt_not_logged(testclient, user, client):
|
||||
"""
|
||||
prompt=none should return a login_required error when no
|
||||
user is logged in.
|
||||
"""Prompt=none should return a login_required error when no user is logged
|
||||
in.
|
||||
|
||||
login_required
|
||||
The Authorization Server requires End-User authentication.
|
||||
This error MAY be returned when the prompt parameter value in the
|
||||
Authentication Request is none, but the Authentication Request
|
||||
cannot be completed without displaying a user interface for End-User
|
||||
authentication.
|
||||
login_required The Authorization Server requires End-User
|
||||
authentication. This error MAY be returned when the prompt
|
||||
parameter value in the Authentication Request is none, but the
|
||||
Authentication Request cannot be completed without displaying a
|
||||
user interface for End-User authentication.
|
||||
"""
|
||||
consent = models.Consent(
|
||||
consent_id=str(uuid.uuid4()),
|
||||
|
@ -79,15 +75,14 @@ def test_prompt_not_logged(testclient, user, client):
|
|||
|
||||
|
||||
def test_prompt_no_consent(testclient, logged_user, client):
|
||||
"""
|
||||
prompt=none should return a consent_required error when user
|
||||
are logged in but have not granted their consent.
|
||||
"""Prompt=none should return a consent_required error when user are logged
|
||||
in but have not granted their consent.
|
||||
|
||||
consent_required
|
||||
The Authorization Server requires End-User consent. This error MAY be
|
||||
returned when the prompt parameter value in the Authentication Request
|
||||
is none, but the Authentication Request cannot be completed without
|
||||
displaying a user interface for End-User consent.
|
||||
consent_required The Authorization Server requires End-User consent.
|
||||
This error MAY be returned when the prompt parameter value in the
|
||||
Authentication Request is none, but the Authentication Request
|
||||
cannot be completed without displaying a user interface for End-User
|
||||
consent.
|
||||
"""
|
||||
res = testclient.get(
|
||||
"/oauth/authorize",
|
||||
|
@ -104,10 +99,8 @@ def test_prompt_no_consent(testclient, logged_user, client):
|
|||
|
||||
|
||||
def test_prompt_create_logged(testclient, logged_user, client):
|
||||
"""
|
||||
If prompt=create and user is already logged in,
|
||||
then go straight to the consent page.
|
||||
"""
|
||||
"""If prompt=create and user is already logged in, then go straight to the
|
||||
consent page."""
|
||||
testclient.app.config["ENABLE_REGISTRATION"] = True
|
||||
|
||||
consent = models.Consent(
|
||||
|
@ -135,16 +128,15 @@ def test_prompt_create_logged(testclient, logged_user, client):
|
|||
|
||||
|
||||
def test_prompt_create_registration_disabled(testclient, trusted_client, smtpd):
|
||||
"""
|
||||
If prompt=create but Canaille registration is disabled,
|
||||
an error response should be returned.
|
||||
"""If prompt=create but Canaille registration is disabled, an error
|
||||
response should be returned.
|
||||
|
||||
If the OpenID Provider receives a prompt value that it does
|
||||
not support (not declared in the prompt_values_supported
|
||||
metadata field) the OP SHOULD respond with an HTTP 400 (Bad
|
||||
Request) status code and an error value of invalid_request.
|
||||
It is RECOMMENDED that the OP return an error_description
|
||||
value identifying the invalid parameter value.
|
||||
If the OpenID Provider receives a prompt value that it does not
|
||||
support (not declared in the prompt_values_supported metadata field)
|
||||
the OP SHOULD respond with an HTTP 400 (Bad Request) status code and
|
||||
an error value of invalid_request. It is RECOMMENDED that the OP
|
||||
return an error_description value identifying the invalid parameter
|
||||
value.
|
||||
"""
|
||||
res = testclient.get(
|
||||
"/oauth/authorize",
|
||||
|
@ -164,11 +156,11 @@ def test_prompt_create_registration_disabled(testclient, trusted_client, smtpd):
|
|||
|
||||
|
||||
def test_prompt_create_not_logged(testclient, trusted_client, smtpd):
|
||||
"""
|
||||
If prompt=create and user is not logged in,
|
||||
then display the registration form.
|
||||
Check that the user is correctly redirected to
|
||||
the client page after the registration process.
|
||||
"""If prompt=create and user is not logged in, then display the
|
||||
registration form.
|
||||
|
||||
Check that the user is correctly redirected to the client page after
|
||||
the registration process.
|
||||
"""
|
||||
testclient.app.config["ENABLE_REGISTRATION"] = True
|
||||
|
||||
|
|
Loading…
Reference in a new issue