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