chore: add docformatter pre-commit

This commit is contained in:
Éloi Rivard 2023-12-28 18:31:57 +01:00
parent e8b620588e
commit 395b6ab4f3
No known key found for this signature in database
GPG key ID: 7EDA204EA57DD184
14 changed files with 412 additions and 538 deletions

View file

@ -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:

View file

@ -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

View file

@ -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 = {}

View file

@ -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

View file

@ -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"

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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()

View file

@ -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(

View file

@ -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"))

View file

@ -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