Beispiel #1
0
class MailOutput(Schema):
    html_body = fields.Str()
    plain_body = fields.Str()
    subject = fields.Str()
    # # This is because Email is not typed on marshmallow
    to = fields.Email()  # type: ignore
    cc = fields.List(fields.Email())  # type: ignore
    bcc = fields.List(fields.Email())  # type: ignore
Beispiel #2
0
def getProfileData():
    # as defined in Marshmallow.schema.from_dict
    attributes: Dict[str, Union[fields.Field, type]] = {}

    attributes["uuid"] = fields.UUID(required=True)
    attributes["email"] = fields.Email(required=True)
    attributes["name"] = fields.Str(required=True)
    attributes["surname"] = fields.Str(required=True)
    attributes["isAdmin"] = fields.Boolean(required=True)
    attributes["isStaff"] = fields.Boolean(required=True)
    attributes["isCoordinator"] = fields.Boolean(required=True)
    attributes["privacy_accepted"] = fields.Boolean(required=True)
    attributes["is_active"] = fields.Boolean(required=True)
    attributes["expiration"] = fields.DateTime(allow_none=True, format=ISO8601UTC)
    attributes["roles"] = fields.Dict(required=True)
    attributes["last_password_change"] = fields.DateTime(
        required=True, format=ISO8601UTC
    )
    attributes["first_login"] = fields.DateTime(required=True, format=ISO8601UTC)
    attributes["last_login"] = fields.DateTime(required=True, format=ISO8601UTC)

    attributes["group"] = fields.Nested(Group)

    attributes["two_factor_enabled"] = fields.Boolean(required=True)

    if custom_fields := mem.customizer.get_custom_output_fields(None):
        attributes.update(custom_fields)
Beispiel #3
0
def profile_output() -> Schema:
    attributes: MarshmallowSchema = {}

    attributes["uuid"] = fields.UUID(required=True)
    # This is because Email is not typed on marshmallow
    attributes["email"] = fields.Email(required=True)  # type: ignore
    attributes["name"] = fields.Str(required=True)
    attributes["surname"] = fields.Str(required=True)
    attributes["isAdmin"] = fields.Boolean(required=True)
    attributes["isStaff"] = fields.Boolean(required=True)
    attributes["isCoordinator"] = fields.Boolean(required=True)
    attributes["privacy_accepted"] = fields.Boolean(required=True)
    attributes["is_active"] = fields.Boolean(required=True)
    attributes["expiration"] = fields.DateTime(allow_none=True,
                                               format=ISO8601UTC)
    attributes["roles"] = fields.Dict(required=True)
    attributes["last_password_change"] = fields.DateTime(required=True,
                                                         format=ISO8601UTC)
    attributes["first_login"] = fields.DateTime(required=True,
                                                format=ISO8601UTC)
    attributes["last_login"] = fields.DateTime(required=True,
                                               format=ISO8601UTC)

    if Connector.authentication_service == "neo4j":
        attributes["belongs_to"] = fields.Neo4jRelationshipToSingle(
            Group, data_key="group")
    else:
        attributes["belongs_to"] = fields.Nested(Group, data_key="group")

    attributes["two_factor_enabled"] = fields.Boolean(required=True)

    if custom_fields := mem.customizer.get_custom_output_fields(None):
        attributes.update(custom_fields)
Beispiel #4
0
def user_registration_input(request: FlaskRequest) -> Type[Schema]:

    attributes: MarshmallowSchema = {}

    attributes["name"] = fields.Str(required=True)
    attributes["surname"] = fields.Str(required=True)
    # This is because Email is not typed on marshmallow
    attributes["email"] = fields.Email(  # type: ignore
        required=True,
        metadata={"label": "Username (email address)"},
        validate=validate.Length(max=100),
    )
    attributes["password"] = fields.Str(
        required=True,
        validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
        metadata={"password": True},
    )
    attributes["password_confirm"] = fields.Str(
        required=True,
        validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
        metadata={
            "label": "Password confirmation",
            "password": True
        },
    )

    if custom_fields := mem.customizer.get_custom_input_fields(
            request=None, scope=mem.customizer.REGISTRATION):
        attributes.update(custom_fields)
Beispiel #5
0
def admin_user_output(many: bool = True) -> Schema:
    attributes: MarshmallowSchema = {}

    attributes["uuid"] = fields.UUID()
    # This is because Email is not typed on marshmallow
    attributes["email"] = fields.Email()  # type: ignore
    attributes["name"] = fields.Str()
    attributes["surname"] = fields.Str()
    attributes["first_login"] = fields.DateTime(allow_none=True,
                                                format=ISO8601UTC)
    attributes["last_login"] = fields.DateTime(allow_none=True,
                                               format=ISO8601UTC)
    attributes["last_password_change"] = fields.DateTime(allow_none=True,
                                                         format=ISO8601UTC)
    attributes["is_active"] = fields.Boolean()
    attributes["privacy_accepted"] = fields.Boolean()
    attributes["roles"] = fields.List(fields.Nested(Role))
    attributes["expiration"] = fields.DateTime(allow_none=True,
                                               format=ISO8601UTC)

    if Connector.authentication_service == "neo4j":
        attributes["belongs_to"] = fields.Neo4jRelationshipToSingle(
            Group, data_key="group")
    else:
        attributes["belongs_to"] = fields.Nested(Group, data_key="group")

    if custom_fields := mem.customizer.get_custom_output_fields(None):
        attributes.update(custom_fields)
Beispiel #6
0
class LoginsSchema(Schema):
    # This is because Email is not typed on marshmallow
    username = fields.Email()  # type: ignore
    date = fields.DateTime(format=ISO8601UTC)
    IP = fields.Str()
    location = fields.Str()
    failed = fields.Boolean()
    flushed = fields.Boolean()
Beispiel #7
0
    def build_schema(self, model: Type[Any]) -> None:

        # Get the full list of parent classes from model to object
        classes = inspect.getmro(model)

        starting_point = False
        # Iterate in reversed order to start from object
        for c in reversed(classes):
            # Skip all parentes up to StructuredNode and StructuredRel (included)
            if not starting_point:
                # Found the starting point, next class will be descended up to model
                if c == StructuredNode or c == StructuredRel:
                    starting_point = True
                # skip all parent up to StructuredNode and StructuredRel INCLUDED
                continue

            # Iterate all class attributes to find neomodel properties
            for attribute in c.__dict__:
                prop = getattr(c, attribute)

                if not isinstance(prop, properties.Property):
                    continue

                # self.fields can be None when the special value * is given in input
                if self.fields and attribute not in self.fields:
                    continue

                # log.info("Including property {}.{}", model.__name__, attribute)
                if isinstance(prop, properties.StringProperty):
                    if prop.choices is None:
                        self.declared_fields[attribute] = fields.Str()
                    else:
                        self.declared_fields[attribute] = fields.Neo4jChoice(
                            prop.choices)

                elif isinstance(prop, properties.BooleanProperty):
                    self.declared_fields[attribute] = fields.Boolean()
                elif isinstance(prop, properties.IntegerProperty):
                    self.declared_fields[attribute] = fields.Integer()
                elif isinstance(prop, properties.FloatProperty):
                    self.declared_fields[attribute] = fields.Float()
                elif isinstance(prop, properties.EmailProperty):
                    # This is because Nested is not typed on marshmallow
                    self.declared_fields[attribute] = fields.Email(
                    )  # type: ignore
                elif isinstance(prop, properties.DateTimeProperty):
                    self.declared_fields[attribute] = fields.AwareDateTime()
                elif isinstance(prop, properties.DateProperty):
                    self.declared_fields[attribute] = fields.Date()
                elif isinstance(prop, properties.UniqueIdProperty):
                    self.declared_fields[attribute] = fields.Str()
                else:  # pragma: no cover
                    log.error(
                        "Unsupport neomodel property: {}, fallback to StringProperty",
                        prop.__class__.__name__,
                    )
                    self.declared_fields[attribute] = fields.Str()
Beispiel #8
0
def group_users_output() -> Schema:
    attributes: MarshmallowSchema = {}

    # This is because Email is not typed on marshmallow
    attributes["email"] = fields.Email()  # type: ignore
    attributes["name"] = fields.Str()
    attributes["surname"] = fields.Str()
    attributes["roles"] = fields.List(fields.Nested(Role))

    if custom_fields := mem.customizer.get_custom_output_fields(None):
        attributes.update(custom_fields)
Beispiel #9
0
class MailInput(Schema):
    subject = fields.Str(required=True,
                         metadata={"description": "Subject of your email"})
    body = fields.Str(
        required=True,
        validate=validate.Length(max=9999),
        metadata={
            "description": "Body of your email. You can use html code here."
        },
    )
    # This is because Email is not typed on marshmallow
    to = fields.Email(  # type: ignore
        required=True,
        metadata={"label": "Destination email address"})
    cc = fields.DelimitedList(
        # This is because Email is not typed on marshmallow
        fields.Email(),  # type: ignore
        metadata={
            "label": "CC - Carbon Copy",
            "description": "CC email addresses (comma-delimited list)",
        },
    )
    bcc = fields.DelimitedList(
        # This is because Email is not typed on marshmallow
        fields.Email(),  # type: ignore
        metadata={
            "label": "BCC - Blind Carbon Copy",
            "description": "BCC email addresses (comma-delimited list)",
        },
    )
    dry_run = fields.Boolean(
        required=True,
        metadata={
            "label": "Dry run execution",
            "description": "Only simulate the email, do not send it",
        },
    )
Beispiel #10
0
class Credentials(Schema):
    username = fields.Email(required=True)
    password = fields.Str(
        required=True,
        password=True,
        # Otherwise default testing password, like test, will fail
        # validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH)
    )
    new_password = fields.Str(
        required=False,
        password=True,
        validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
    )
    password_confirm = fields.Str(
        required=False,
        password=True,
        validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
    )
    totp_code = TOTP(required=False)
Beispiel #11
0
class Credentials(Schema):
    # This is because Email is not typed on marshmallow
    username = fields.Email(  # type: ignore
        required=True, validate=validate.Length(max=100))
    password = fields.Str(
        required=True,
        metadata={"password": True},
        # Otherwise default testing password, like test, will fail
        # validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH)
    )
    new_password = fields.Str(
        required=False,
        validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
        metadata={"password": True},
    )
    password_confirm = fields.Str(
        required=False,
        validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
        metadata={"password": True},
    )
    totp_code = fields.TOTP(required=False)
Beispiel #12
0
    def getInputSchema(request):

        # as defined in Marshmallow.schema.from_dict
        attributes: Dict[str, Union[fields.Field, type]] = {}

        attributes["name"] = fields.Str(required=True)
        attributes["surname"] = fields.Str(required=True)
        attributes["email"] = fields.Email(required=True,
                                           label="Username (email address)")
        attributes["password"] = fields.Str(
            required=True,
            password=True,
            validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
        )
        attributes["password_confirm"] = fields.Str(
            required=True,
            password=True,
            label="Password confirmation",
            validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
        )

        if custom_fields := mem.customizer.get_custom_input_fields(
                request=None, scope=mem.customizer.REGISTRATION):
            attributes.update(custom_fields)
Beispiel #13
0
def get_output_schema():
    # as defined in Marshmallow.schema.from_dict
    attributes: Dict[str, Union[fields.Field, type]] = {}

    attributes["uuid"] = fields.UUID()
    attributes["email"] = fields.Email()
    attributes["name"] = fields.Str()
    attributes["surname"] = fields.Str()
    attributes["first_login"] = fields.DateTime(allow_none=True,
                                                format=ISO8601UTC)
    attributes["last_login"] = fields.DateTime(allow_none=True,
                                               format=ISO8601UTC)
    attributes["last_password_change"] = fields.DateTime(allow_none=True,
                                                         format=ISO8601UTC)
    attributes["is_active"] = fields.Boolean()
    attributes["privacy_accepted"] = fields.Boolean()
    attributes["roles"] = fields.List(fields.Nested(Roles))
    attributes["expiration"] = fields.DateTime(allow_none=True,
                                               format=ISO8601UTC)

    attributes["belongs_to"] = fields.Nested(Group, data_key="group")

    if custom_fields := mem.customizer.get_custom_output_fields(None):
        attributes.update(custom_fields)
Beispiel #14
0
class ProfileActivation(EndpointResource):
    depends_on = ["MAIN_LOGIN_ENABLE", "ALLOW_REGISTRATION"]
    baseuri = "/auth"
    labels = ["base", "profiles"]

    @decorators.endpoint(
        path="/profile/activate/<token>",
        summary="Activate your account by providing the activation token",
        responses={200: "Account successfully activated"},
    )
    def put(self, token: str) -> Response:

        token = token.replace("%2B", ".")
        token = token.replace("+", ".")
        try:
            unpacked_token = self.auth.verify_token(
                token, raiseErrors=True, token_type=self.auth.ACTIVATE_ACCOUNT)

        # If token is expired
        except ExpiredSignatureError:
            raise BadRequest(
                "Invalid activation token: this request is expired", )

        # if token is not yet active
        except ImmatureSignatureError:
            raise BadRequest("Invalid activation token")

        # if token does not exist (or other generic errors)
        except BaseException:
            raise BadRequest("Invalid activation token")

        user = unpacked_token[3]
        self.auth.verify_blocked_username(user.email)

        # Recovering token object from jti
        jti = unpacked_token[2]
        token_obj = self.auth.get_tokens(token_jti=jti)
        # Cannot be tested, this is an extra test to prevent any unauthorized access...
        # but invalid tokens are already refused above, with auth.verify_token
        if len(token_obj) == 0:  # pragma: no cover
            raise BadRequest(
                "Invalid activation token: this request is no longer valid")

        # If user logged is already active, invalidate the token
        if user.is_active:
            self.auth.invalidate_token(token)
            raise BadRequest(
                "Invalid activation token: this request is no longer valid")

        # The activation token is valid, do something
        user.is_active = True
        self.auth.save_user(user)

        # Bye bye token (activation tokens are valid only once)
        self.auth.invalidate_token(token)

        self.log_event(self.events.activation, user=user, target=user)

        return self.response("Account activated")

    @decorators.use_kwargs({"username": fields.Email(required=True)})
    @decorators.endpoint(
        path="/profile/activate",
        summary="Ask a new activation link",
        responses={200: "A new activation link has been sent"},
    )
    def post(self, username: str) -> Response:

        self.auth.verify_blocked_username(username)

        user = self.auth.get_user(username=username)

        # if user is None this endpoint does nothing but the response
        # remain the same to prevent any user guessing
        if user is not None:
            smtp_client = smtp.get_instance()
            send_activation_link(smtp_client, self.auth, user)
        msg = ("We are sending an email to your email address where "
               "you will find the link to activate your account")
        return self.response(msg)
Beispiel #15
0
class ProfileActivation(EndpointResource):
    depends_on = ["MAIN_LOGIN_ENABLE", "ALLOW_REGISTRATION", "AUTH_ENABLE"]
    labels = ["profile"]

    @decorators.endpoint(
        path="/auth/profile/activate/<token>",
        summary="Activate your account by providing the activation token",
        responses={
            200: "Account successfully activated",
            400: "Invalid token",
            403:
            "Account temporarily blocked due to the number of failed logins",
        },
    )
    def put(self, token: str) -> Response:

        token = token.replace("%2B", ".")
        token = token.replace("+", ".")

        try:
            # valid, token, jti, user
            _, _, jti, user = self.auth.verify_token(
                token, raiseErrors=True, token_type=self.auth.ACTIVATE_ACCOUNT)

        # If token is expired
        except ExpiredSignatureError:
            raise BadRequest(
                "Invalid activation token: this request is expired", )

        # if token is not active yet
        except ImmatureSignatureError:
            raise BadRequest("Invalid activation token")

        # if token does not exist (or other generic errors)
        except Exception:
            raise BadRequest("Invalid activation token")

        if user is None:  # pragma: no cover
            raise BadRequest("Invalid activation token")

        self.auth.verify_blocked_username(user.email)

        # Recovering token object from jti
        token_obj = self.auth.get_tokens(token_jti=jti)
        # Cannot be tested, this is an extra test to prevent any unauthorized access...
        # but invalid tokens are already refused above, with auth.verify_token
        if len(token_obj) == 0:  # pragma: no cover
            raise BadRequest(
                "Invalid activation token: this request is no longer valid")

        # If user logged is already active, invalidate the token
        if user.is_active:
            self.auth.invalidate_token(token)
            raise BadRequest(
                "Invalid activation token: this request is no longer valid")

        # The activation token is valid, do something
        user.is_active = True
        self.auth.save_user(user)

        # Bye bye token (activation tokens are valid only once)
        self.auth.invalidate_token(token)

        self.log_event(self.events.activation, user=user, target=user)

        return self.response("Account activated")

    @decorators.use_kwargs(
        # This is because Email is not typed on marshmallow
        {"username": fields.Email(required=True)}  # type: ignore
    )
    @decorators.endpoint(
        path="/auth/profile/activate",
        summary="Ask a new activation link",
        responses={
            200: "A new activation link has been sent",
            403:
            "Account temporarily blocked due to the number of failed logins",
        },
    )
    def post(self, username: str) -> Response:

        self.auth.verify_blocked_username(username)

        user = self.auth.get_user(username=username)

        # if user is None this endpoint does nothing but the response
        # remain the same to prevent any user guessing
        if user is not None:

            auth = Connector.get_authentication_instance()

            activation_token, payload = auth.create_temporary_token(
                user, auth.ACTIVATE_ACCOUNT)

            server_url = get_frontend_url()

            rt = activation_token.replace(".", "+")
            url = f"{server_url}/public/register/{rt}"

            sent = send_activation_link(user, url)

            if not sent:  # pragma: no cover
                raise ServiceUnavailable("Error sending email, please retry")

            auth.save_token(user,
                            activation_token,
                            payload,
                            token_type=auth.ACTIVATE_ACCOUNT)

        msg = ("We are sending an email to your email address where "
               "you will find the link to activate your account")
        return self.response(msg)
Beispiel #16
0
class User(Schema):
    uuid = fields.UUID()
    email = fields.Email()
    name = fields.String()
    surname = fields.String()
Beispiel #17
0
    class RecoverPassword(EndpointResource):

        depends_on = [
            "MAIN_LOGIN_ENABLE", "ALLOW_PASSWORD_RESET", "AUTH_ENABLE"
        ]
        labels = ["authentication"]

        @decorators.use_kwargs(
            # This is because Email is not typed on marshmallow
            {"reset_email": fields.Email(required=True)}  # type: ignore
        )
        @decorators.endpoint(
            path="/auth/reset",
            summary="Request password reset via email",
            description="Request password reset via email",
            responses={
                200: "Reset email is valid",
                400: "Invalid reset email",
                403: "Account not found or already active",
            },
        )
        def post(self, reset_email: str) -> Response:

            reset_email = reset_email.lower()

            self.auth.verify_blocked_username(reset_email)

            user = self.auth.get_user(username=reset_email)

            if user is None:
                raise Forbidden(
                    f"Sorry, {reset_email} is not recognized as a valid username",
                )

            self.auth.verify_user_status(user)

            reset_token, payload = self.auth.create_temporary_token(
                user, self.auth.PWD_RESET)

            server_url = get_frontend_url()

            rt = reset_token.replace(".", "+")

            uri = Env.get("RESET_PASSWORD_URI", "/public/reset")
            complete_uri = f"{server_url}{uri}/{rt}"

            sent = send_password_reset_link(user, complete_uri, reset_email)

            if not sent:  # pragma: no cover
                raise ServiceUnavailable("Error sending email, please retry")

            ##################
            # Completing the reset task
            self.auth.save_token(user,
                                 reset_token,
                                 payload,
                                 token_type=self.auth.PWD_RESET)

            msg = "We'll send instructions to the email provided if it's associated "
            msg += "with an account. Please check your spam/junk folder."

            self.log_event(self.events.reset_password_request, user=user)
            return self.response(msg)

        @decorators.use_kwargs({
            "new_password":
            fields.Str(
                required=False,
                validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
                metadata={"password": True},
            ),
            "password_confirm":
            fields.Str(
                required=False,
                validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
                metadata={"password": True},
            ),
        })
        @decorators.endpoint(
            path="/auth/reset/<token>",
            summary="Change password as conseguence of a reset request",
            description="Change password as conseguence of a reset request",
            responses={
                200: "Reset token is valid, password changed",
                400: "Invalid reset token",
            },
        )
        def put(
            self,
            token: str,
            new_password: Optional[str] = None,
            password_confirm: Optional[str] = None,
        ) -> Response:

            token = token.replace("%2B", ".")
            token = token.replace("+", ".")

            try:
                # valid, token, jti, user
                _, _, jti, user = self.auth.verify_token(
                    token, raiseErrors=True, token_type=self.auth.PWD_RESET)

            # If token is expired
            except jwt.exceptions.ExpiredSignatureError:
                raise BadRequest(
                    "Invalid reset token: this request is expired")

            # if token is not active yet
            except jwt.exceptions.ImmatureSignatureError as e:
                log.info(e)
                raise BadRequest("Invalid reset token")
            # if token does not exist (or other generic errors)
            except Exception as e:
                log.info(e)
                raise BadRequest("Invalid reset token")

            if user is None:  # pragma: no cover
                raise BadRequest("Invalid activation token")

            # Recovering token object from jti
            tokens_obj = self.auth.get_tokens(token_jti=jti)
            # Can't happen because the token is refused from verify_token function
            if len(tokens_obj) == 0:  # pragma: no cover
                raise BadRequest(
                    "Invalid reset token: this request is no longer valid")

            token_obj = tokens_obj.pop(0)
            emitted = token_obj["emitted"]

            last_change = None
            # If user logged in after the token emission invalidate the token
            if user.last_login is not None:
                last_change = user.last_login
            # If user changed the pwd after the token emission invalidate the token
            # Can't happen because the change password also invalidated the token
            elif user.last_password_change is not None:  # pragma: no cover
                last_change = user.last_password_change

            if last_change is not None:

                # Can't happen because the change password also invalidated the token
                if last_change > emitted:  # pragma: no cover
                    self.auth.invalidate_token(token)
                    raise BadRequest(
                        "Invalid reset token: this request is no longer valid",
                    )

            # The reset token is valid, do something

            # No password to be changed, just a token verification
            if new_password is None and password_confirm is None:
                return self.empty_response()

            # Something is missing
            if new_password is None or password_confirm is None:
                raise BadRequest("Invalid password")

            if new_password != password_confirm:
                raise BadRequest(
                    "New password does not match with confirmation")

            self.auth.change_password(user, user.password, new_password,
                                      password_confirm)
            # I really don't know why this save is required... since it is already
            # in change_password ... But if I remove it the new pwd is not saved...
            self.auth.save_user(user)

            # Bye bye token (reset tokens are valid only once)
            self.auth.invalidate_token(token)

            return self.response("Password changed")
Beispiel #18
0
class User(Schema):
    email = fields.Email()
    name = fields.Str()
    surname = fields.Str()
Beispiel #19
0
class UserWithUUID(Schema):
    uuid = fields.UUID()
    # This is because Email is not typed on marshmallow
    email = fields.Email()  # type: ignore
    name = fields.String()
    surname = fields.String()
Beispiel #20
0
def admin_user_input(request: FlaskRequest, is_post: bool) -> Type[Schema]:

    is_admin = HTTPTokenAuth.is_session_user_admin(request, auth)

    attributes: MarshmallowSchema = {}
    if is_post:
        # This is because Email is not typed on marshmallow
        attributes["email"] = fields.Email(  # type: ignore
            required=is_post,
            validate=validate.Length(max=100))

    attributes["name"] = fields.Str(
        required=is_post,
        validate=validate.Length(min=1),
        metadata={"label": "First Name"},
    )
    attributes["surname"] = fields.Str(
        required=is_post,
        validate=validate.Length(min=1),
        metadata={"label": "Last Name"},
    )

    attributes["password"] = fields.Str(
        required=is_post,
        validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
        metadata={"password": True},
    )

    if Connector.check_availability("smtp"):
        attributes["email_notification"] = fields.Bool(
            metadata={"label": "Notify password by email"})

    attributes["is_active"] = fields.Bool(
        dump_default=True,
        required=False,
        metadata={"label": "Activate user"},
    )

    roles = {r.name: r.description for r in auth.get_roles()}
    if not is_admin and RoleEnum.ADMIN.value in roles:
        roles.pop(RoleEnum.ADMIN.value)

    attributes["roles"] = fields.List(
        fields.Str(validate=validate.OneOf(
            choices=[r for r in roles.keys()],
            labels=[r for r in roles.values()],
        )),
        dump_default=[auth.default_role],
        required=False,
        unique=True,
        metadata={
            "label": "Roles",
            "description": "",
            "extra_descriptions": auth.role_descriptions,
        },
    )

    group_keys = []
    group_labels = []

    for g in auth.get_groups():
        group_keys.append(g.uuid)
        group_labels.append(f"{g.shortname} - {g.fullname}")

    if len(group_keys) == 1:
        default_group = group_keys[0]
    else:
        default_group = None

    attributes["group"] = fields.Str(
        required=is_post,
        dump_default=default_group,
        validate=validate.OneOf(choices=group_keys, labels=group_labels),
        metadata={
            "label": "Group",
            "description": "The group to which the user belongs",
        },
    )

    attributes["expiration"] = fields.DateTime(
        required=False,
        allow_none=True,
        metadata={
            "label": "Account expiration",
            "description": "This user will be blocked after this date",
        },
    )

    if custom_fields := mem.customizer.get_custom_input_fields(
            request=request, scope=mem.customizer.ADMIN):
        attributes.update(custom_fields)
Beispiel #21
0
    def test_responses(self, faker: Faker) -> None:
        class MySchema(Schema):
            name = fields.Str()

        f = "myfield"
        assert (
            ResponseMaker.get_schema_type(f, fields.Str(metadata={"password": True}))
            == "password"
        )
        assert ResponseMaker.get_schema_type(f, fields.Bool()) == "boolean"
        assert ResponseMaker.get_schema_type(f, fields.Boolean()) == "boolean"
        assert ResponseMaker.get_schema_type(f, fields.Date()) == "date"
        assert ResponseMaker.get_schema_type(f, fields.DateTime()) == "datetime"
        assert ResponseMaker.get_schema_type(f, fields.AwareDateTime()) == "datetime"
        assert ResponseMaker.get_schema_type(f, fields.NaiveDateTime()) == "datetime"
        assert ResponseMaker.get_schema_type(f, fields.Decimal()) == "number"
        # This is because Email is not typed on marshmallow
        assert ResponseMaker.get_schema_type(f, fields.Email()) == "email"  # type: ignore
        assert ResponseMaker.get_schema_type(f, fields.Float()) == "number"
        assert ResponseMaker.get_schema_type(f, fields.Int()) == "int"
        assert ResponseMaker.get_schema_type(f, fields.Integer()) == "int"
        assert ResponseMaker.get_schema_type(f, fields.Number()) == "number"
        assert ResponseMaker.get_schema_type(f, fields.Str()) == "string"
        assert ResponseMaker.get_schema_type(f, fields.String()) == "string"
        assert ResponseMaker.get_schema_type(f, fields.Dict()) == "dictionary"
        assert ResponseMaker.get_schema_type(f, fields.List(fields.Str())) == "string[]"
        assert ResponseMaker.get_schema_type(f, fields.Nested(MySchema())) == "nested"
        # Unsupported types, fallback to string
        assert ResponseMaker.get_schema_type(f, fields.URL()) == "string"
        assert ResponseMaker.get_schema_type(f, fields.Url()) == "string"
        assert ResponseMaker.get_schema_type(f, fields.UUID()) == "string"
        # assert ResponseMaker.get_schema_type(f, fields.Constant("x")) == "string"
        assert ResponseMaker.get_schema_type(f, fields.Field()) == "string"
        # assert ResponseMaker.get_schema_type(f, fields.Function()) == "string"
        # assert ResponseMaker.get_schema_type(f, fields.Mapping()) == "string"
        # assert ResponseMaker.get_schema_type(f, fields.Method()) == "string"
        # assert ResponseMaker.get_schema_type(f, fields.Raw()) == "string"
        # assert ResponseMaker.get_schema_type(f, fields.TimeDelta()) == "string"

        assert not ResponseMaker.is_binary(None)
        assert not ResponseMaker.is_binary("")
        assert not ResponseMaker.is_binary("application/json")
        assert ResponseMaker.is_binary("application/octet-stream")
        assert ResponseMaker.is_binary("application/x-bzip")
        assert ResponseMaker.is_binary("application/x-bzip2")
        assert ResponseMaker.is_binary("application/pdf")
        assert ResponseMaker.is_binary("application/msword")
        assert ResponseMaker.is_binary("application/rtf")
        assert ResponseMaker.is_binary("application/x-tar")
        assert ResponseMaker.is_binary("application/gzip")
        assert ResponseMaker.is_binary("application/zip")
        assert ResponseMaker.is_binary("application/x-7z-compressed")
        assert not ResponseMaker.is_binary("text/plain")
        assert not ResponseMaker.is_binary("text/css")
        assert not ResponseMaker.is_binary("text/csv")
        assert not ResponseMaker.is_binary("text/html")
        assert not ResponseMaker.is_binary("text/javascript")
        assert not ResponseMaker.is_binary("text/xml")
        assert ResponseMaker.is_binary("image/gif")
        assert ResponseMaker.is_binary("image/jpeg")
        assert ResponseMaker.is_binary("image/png")
        assert ResponseMaker.is_binary("image/svg+xml")
        assert ResponseMaker.is_binary("image/tiff")
        assert ResponseMaker.is_binary("image/webp")
        assert ResponseMaker.is_binary("image/bmp")
        assert ResponseMaker.is_binary("image/aac")
        assert ResponseMaker.is_binary("audio/midi")
        assert ResponseMaker.is_binary("audio/mpeg")
        assert ResponseMaker.is_binary("audio/wav")
        assert ResponseMaker.is_binary("audio/anyother")
        assert ResponseMaker.is_binary("video/mpeg")
        assert ResponseMaker.is_binary("video/ogg")
        assert ResponseMaker.is_binary("video/webm")
        assert ResponseMaker.is_binary("video/anyother")
        assert ResponseMaker.is_binary("video/anyother")
        assert not ResponseMaker.is_binary(faker.pystr())

        response = EndpointResource.response("", code=200)
        assert response[1] == 200  # type: ignore
        response = EndpointResource.response(None, code=200)
        assert response[1] == 204  # type: ignore
        response = EndpointResource.response(None, code=200, head_method=True)
        assert response[1] == 200  # type: ignore
Beispiel #22
0
class User(Schema):
    # This is because Email is not typed on marshmallow
    email = fields.Email()  # type: ignore
    name = fields.Str()
    surname = fields.Str()
Beispiel #23
0
def getInputSchema(request, is_post):

    # as defined in Marshmallow.schema.from_dict
    attributes: Dict[str, Union[fields.Field, type]] = {}
    if is_post:
        attributes["email"] = fields.Email(required=is_post)

    attributes["name"] = fields.Str(required=is_post,
                                    validate=validate.Length(min=1))
    attributes["surname"] = fields.Str(required=is_post,
                                       validate=validate.Length(min=1))

    attributes["password"] = fields.Str(
        required=is_post,
        password=True,
        validate=validate.Length(min=auth.MIN_PASSWORD_LENGTH),
    )

    if Connector.check_availability("smtp"):
        attributes["email_notification"] = fields.Bool(
            label="Notify password by email")

    attributes["is_active"] = fields.Bool(label="Activate user",
                                          default=True,
                                          required=False)

    roles = {r.name: r.description for r in auth.get_roles()}

    attributes["roles"] = AdvancedList(
        fields.Str(validate=validate.OneOf(
            choices=[r for r in roles.keys()],
            labels=[r for r in roles.values()],
        )),
        required=False,
        label="Roles",
        description="",
        unique=True,
        multiple=True,
    )

    group_keys = []
    group_labels = []

    for g in auth.get_groups():
        group_keys.append(g.uuid)
        group_labels.append(f"{g.shortname} - {g.fullname}")

    if len(group_keys) == 1:
        default_group = group_keys[0]
    else:
        default_group = None

    attributes["group"] = fields.Str(
        label="Group",
        description="The group to which the user belongs",
        required=is_post,
        default=default_group,
        validate=validate.OneOf(choices=group_keys, labels=group_labels),
    )

    attributes["expiration"] = fields.DateTime(
        required=False,
        allow_none=True,
        label="Account expiration",
        description="This user will be blocked after this date",
    )

    if custom_fields := mem.customizer.get_custom_input_fields(
            request=request, scope=mem.customizer.ADMIN):
        attributes.update(custom_fields)