Beispiel #1
0
def initialize_testing_environment(auth: BaseAuthentication) -> None:

    assert TESTING

    faker = get_faker()
    email = faker.ascii_email()
    password = faker.password(strong=True)
    default_group = auth.get_group(name=DEFAULT_GROUP_NAME)
    user = auth.create_user(
        {
            "email": email,
            "name": "Default",
            "surname": "User",
            "password": password,
            "last_password_change": datetime.now(pytz.utc),
        },
        # It will be expanded with the default role
        roles=[],
    )
    auth.add_user_to_group(user, default_group)
    # This is required to execute the commit on sqlalchemy...
    auth.save_user(user)

    for _ in range(0, 20):
        payload, full_payload = auth.fill_payload(user)
        token = auth.create_token(payload)
        auth.save_token(user, token, full_payload)
Beispiel #2
0
    def is_session_user_admin(request: FlaskRequest,
                              auth: BaseAuthentication) -> bool:
        if not request:
            return False

        _, token = HTTPTokenAuth.get_authorization_token(
            allow_access_token_parameter=False)
        _, _, _, user = auth.verify_token(token)

        return auth.is_admin(user)
Beispiel #3
0
def verify_token_is_not_valid(auth: BaseAuthentication,
                              token: str,
                              ttype: Optional[str] = None) -> None:
    unpacked_token = auth.verify_token(token, token_type=ttype)
    assert not unpacked_token[0]
    assert unpacked_token[1] is None
    assert unpacked_token[2] is None
    assert unpacked_token[3] is None

    with pytest.raises(Exception):
        auth.verify_token(token, token_type=ttype, raiseErrors=True)
    def do_login(self,
                 client,
                 USER,
                 PWD,
                 status_code=hcodes.HTTP_OK_BASIC,
                 error=None,
                 **kwargs):
        """
            Make login and return both token and authorization header
        """

        if USER is None or PWD is None:
            BaseAuthentication.myinit()
            if USER is None:
                USER = BaseAuthentication.default_user
            if PWD is None:
                PWD = BaseAuthentication.default_password

        # AUTH_MAX_LOGIN_ATTEMPTS=0
        # AUTH_REGISTER_FAILED_LOGIN=False

        # AUTH_SECOND_FACTOR_AUTHENTICATION=None

        # AUTH_DISABLE_UNUSED_CREDENTIALS_AFTER=0
        # AUTH_MAX_PASSWORD_VALIDITY=0

        data = {'username': USER, 'password': PWD}
        for v in kwargs:
            data[v] = kwargs[v]

        r = client.post(AUTH_URI + '/login', data=json.dumps(data))

        if r.status_code != hcodes.HTTP_OK_BASIC:
            # VERY IMPORTANT FOR DEBUGGING WHEN ADVANCED AUTH OPTIONS ARE ON
            c = json.loads(r.data.decode('utf-8'))
            log.error(c['Response']['errors'])

        assert r.status_code == status_code

        content = json.loads(r.data.decode('utf-8'))
        if error is not None:
            errors = content['Response']['errors']
            if errors is not None:
                assert errors[0] == error

        token = ''
        if content is not None:
            data = content.get('Response', {}).get('data', {})
            if data is not None:
                token = data.get('token', '')
        return {'Authorization': 'Bearer ' + token}, token
Beispiel #5
0
def verify_token_is_not_valid(auth: BaseAuthentication,
                              token: str,
                              ttype: Optional[str] = None) -> None:
    unpacked_token = auth.verify_token(token, token_type=ttype)
    assert not unpacked_token[0]
    assert unpacked_token[1] is None
    assert unpacked_token[2] is None
    assert unpacked_token[3] is None

    try:
        auth.verify_token(token, token_type=ttype, raiseErrors=True)
        pytest.fail("No exception raised")  # pragma: no cover
    except BaseException:
        pass
Beispiel #6
0
    def setUp(self):
        """
        Note: in this base tests,
        I also want to check if i can run multiple Flask applications.

        Thi is why i prefer setUp on setUpClass
        """
        log.debug('### Setting up the Flask server ###')
        app = create_app(testing_mode=True)
        self.app = app.test_client()

        # Auth init from base/custom config
        ba.myinit()
        self._username = ba.default_user
        self._password = ba.default_password
Beispiel #7
0
    def change_password(self, user, password, new_password, password_confirm):

        if new_password != password_confirm:
            msg = "Your password doesn't match the confirmation"
            raise RestApiException(msg, status_code=hcodes.HTTP_BAD_CONFLICT)

        if self.auth.VERIFY_PASSWORD_STRENGTH:
            check = True
            if password is not None:
                check, msg = self.verify_password_strength(
                    new_password, old_pwd=password
                )
            else:
                check, msg = self.verify_password_strength(
                    new_password, old_hash=user.password
                )

            if not check:
                raise RestApiException(msg, status_code=hcodes.HTTP_BAD_CONFLICT)

        if new_password is not None and password_confirm is not None:
            now = datetime.now(pytz.utc)
            user.password = BaseAuthentication.get_password_hash(new_password)
            user.last_password_change = now
            self.auth.save_user(user)

            tokens = self.auth.get_tokens(user=user)
            for token in tokens:
                self.auth.invalidate_token(token=token["token"])
            # changes the user uuid invalidating all tokens
            self.auth.invalidate_all_tokens()

        return True
Beispiel #8
0
    def verify_password_strength(self, pwd, old_pwd=None, old_hash=None):

        if old_pwd is not None and pwd == old_pwd:
            return False, "The new password cannot match the previous password"
        if old_hash is not None:
            new_hash = BaseAuthentication.get_password_hash(pwd)
            if old_hash == new_hash:
                return False, "The new password cannot match the previous password"

        # FIXME: min length should configurable?
        if len(pwd) < 8:
            return False, "Password is too short, use at least 8 characters"

        if not re.search("[a-z]", pwd):
            return False, "Password is too weak, missing lower case letters"
        if not re.search("[A-Z]", pwd):
            return False, "Password is too weak, missing upper case letters"
        if not re.search("[0-9]", pwd):
            return False, "Password is too weak, missing numbers"

        # special_characters = "['\s!#$%&\"(),*+,-./:;<=>?@[\\]^_`{|}~']"
        special_characters = "[^a-zA-Z0-9]"
        if not re.search(special_characters, pwd):
            return False, "Password is too weak, missing special characters"

        return True, None
Beispiel #9
0
def verify_token_is_valid(auth: BaseAuthentication,
                          token: str,
                          ttype: Optional[str] = None) -> None:
    unpacked_token = auth.verify_token(token, token_type=ttype)
    assert unpacked_token[0]
    assert unpacked_token[1] is not None
    assert unpacked_token[2] is not None
    assert unpacked_token[3] is not None
Beispiel #10
0
def handle_response(response):

    response.headers["_RV"] = str(version)

    PROJECT_VERSION = get_project_configuration("project.version", default=None)
    if PROJECT_VERSION is not None:
        response.headers["Version"] = str(PROJECT_VERSION)
    # If it is an upload, DO NOT consume request.data or request.json,
    # otherwise the content gets lost
    try:
        if request.mimetype in ["application/octet-stream", "multipart/form-data"]:
            data = "STREAM_UPLOAD"
        elif request.data:
            data = handle_log_output(request.data)
        elif request.form:
            data = obfuscate_dict(request.form)
        else:
            data = ""

        if data:
            data = f" {data}"
    except Exception as e:  # pragma: no cover
        log.debug(e)
        data = ""

    url = obfuscate_query_parameters(request.url)

    if GZIP_ENABLE and "gzip" in request.headers.get("Accept-Encoding", "").lower():
        response.direct_passthrough = False
        content, headers = ResponseMaker.gzip_response(
            response.data,
            response.status_code,
            response.headers.get("Content-Encoding"),
            response.headers.get("Content-Type"),
        )
        if content:
            response.data = content

            try:
                response.headers.update(headers)
            # Back-compatibility for Werkzeug 0.16.1 as used in B2STAGE
            except AttributeError:  # pragma: no cover
                for k, v in headers.items():
                    response.headers.set(k, v)

    resp = str(response).replace("<Response ", "").replace(">", "")
    log.info(
        "{} {} {}{} -> {}",
        BaseAuthentication.get_remote_ip(),
        request.method,
        url,
        data,
        resp,
    )

    return response
Beispiel #11
0
def prepare_message(instance, user=None, isjson=False, **params):
    """
{ # start
    "request_id": # build a hash for the current request
    "edmo_code": # which eudat centre is
    "json": "the json input file" # parameters maris
    "datetime":"20180328T10:08:30", # timestamp
    "ip_number":"544.544.544.544", # request.remote_addr
    "program":"program/function name", # what's mine?
    "url" ?
    "user":"******", # from maris? marine id?
    "log_string":"start"
}

{ # end
    "datetime":"20180328T10:08:30",
    "request_id":"from the json input file",
    "ip_number":"544.544.544.544",
    "program":"program of function = name",
    "user":"******",
    "edmo_code":"sample 353",
    "log_string":"end"
}
    """
    logmsg = dict(params)

    instance_id = str(id(instance))
    logmsg['request_id'] = instance_id
    # logmsg['request_id'] = instance_id[len(instance_id) - 6:]

    from seadata.apis.commons.seadatacloud import seadata_vars
    logmsg['edmo_code'] = seadata_vars.get('edmo_code')

    from datetime import datetime
    logmsg['datetime'] = datetime.now().strftime("%Y%m%dT%H:%M:%S")

    if isjson:
        return logmsg  # TODO Why this? Why does isjson exist at all?

    from restapi.services.authentication import BaseAuthentication as Service
    ip = Service.get_remote_ip()
    logmsg['ip_number'] = ip

    from flask import request
    # http://localhost:8080/api/pids/<PID>
    import re
    endpoint = re.sub(r"https?://[^\/]+", '', request.url)
    logmsg['program'] = request.method + ':' + endpoint
    if user is None:
        user = '******'  # TODO: True? Not sure!
    logmsg['user'] = user

    # log.pp(logmsg)
    return logmsg
Beispiel #12
0
def handle_response(response: FlaskResponse) -> FlaskResponse:

    response.headers["_RV"] = str(version)

    PROJECT_VERSION = get_project_configuration("project.version", default="0")
    if PROJECT_VERSION is not None:
        response.headers["Version"] = str(PROJECT_VERSION)

    data_string = get_data_from_request()

    url = obfuscate_query_parameters(request.url)

    if (GZIP_ENABLE and not response.is_streamed
            and "gzip" in request.headers.get("Accept-Encoding", "").lower()):
        response.direct_passthrough = False
        content, headers = ResponseMaker.gzip_response(
            response.data,
            response.status_code,
            response.headers.get("Content-Encoding"),
            response.headers.get("Content-Type"),
        )
        if content:
            response.data = content
            response.headers.update(headers)

    resp = str(response).replace("<Response ", "").replace(">", "")
    ip = BaseAuthentication.get_remote_ip(raise_warnings=False)

    is_healthcheck = (ip == "127.0.0.1" and request.method == "GET"
                      and url == "/api/status")
    if is_healthcheck:
        log.debug(
            "{} {} {}{} -> {} [HEALTHCHECK]",
            ip,
            request.method,
            url,
            data_string,
            resp,
        )
    else:
        log.info(
            "{} {} {}{} -> {}",
            ip,
            request.method,
            url,
            data_string,
            resp,
        )

    return response
Beispiel #13
0
    def log_event(
        self,
        event: Events,
        target: Optional[Any] = None,
        payload: Optional[Dict[str, Any]] = None,
        user: Optional[Any] = None,
    ) -> None:

        if not user:
            user = self.get_user()

        save_event_log(
            event=event,
            target=target,
            payload=payload,
            user=user,
            ip=BaseAuthentication.get_remote_ip(),
        )
Beispiel #14
0
    def log_event(
        self,
        event: Events,
        target: Optional[Any] = None,
        payload: Optional[Dict[str, Any]] = None,
        user: Optional[Any] = None,
    ) -> None:

        if not user:
            user = self.auth.get_user(user_id=self.authorized_user)

        save_event_log(
            event=event,
            target=target,
            payload=payload,
            user=user,
            ip=BaseAuthentication.get_remote_ip(),
            url=request.path,
        )
Beispiel #15
0
    def put(self, user_id: str, target_user: User, user: User,
            **kwargs: Any) -> Response:

        if "password" in kwargs:
            unhashed_password = kwargs["password"]
            kwargs["password"] = BaseAuthentication.get_password_hash(
                kwargs["password"])
        else:
            unhashed_password = None

        payload = kwargs.copy()
        roles: List[str] = kwargs.pop("roles", [])

        # The role is already refused by webards... This is an additional check
        # to improve the security, but can't be reached
        if not self.auth.is_admin(
                user) and Role.ADMIN in roles:  # pragma: no cover
            raise Forbidden("This role is not allowed")

        group_id = kwargs.pop("group", None)

        email_notification = kwargs.pop("email_notification", False)

        self.auth.link_roles(target_user, roles)

        userdata, extra_userdata = self.auth.custom_user_properties_pre(kwargs)

        prev_expiration = target_user.expiration

        # mypy correctly raises errors because update_properties is not defined
        # in generic Connector instances, but in this case this is an instance
        # of an auth db and their implementation always contains this method
        self.auth.db.update_properties(target_user, userdata)  # type: ignore

        self.auth.custom_user_properties_post(target_user, userdata,
                                              extra_userdata, self.auth.db)

        self.auth.save_user(target_user)

        if group_id is not None:
            group = self.auth.get_group(group_id=group_id)
            if not group:
                # Can't be reached because group_id is prefiltered by marshmallow
                raise NotFound(
                    "This group cannot be found")  # pragma: no cover

            self.auth.add_user_to_group(target_user, group)

        if email_notification and unhashed_password is not None:
            notify_update_credentials_to_user(target_user, unhashed_password)

        if target_user.expiration:
            # Set expiration on a previously non-expiring account
            # or update the expiration by reducing the validity period
            # In both cases tokens should be invalited to prevent to have tokens
            # with TTL > account validity

            # dt_lower (alias for date_lower_than) is a comparison fn that ignores tz
            if not prev_expiration or dt_lower(target_user.expiration,
                                               prev_expiration):
                for token in self.auth.get_tokens(user=target_user):
                    # Invalidate all tokens with expiration after the account expiration
                    if dt_lower(target_user.expiration, token["expiration"]):
                        self.auth.invalidate_token(token=token["token"])

        self.log_event(self.events.modify, target_user, payload)

        return self.empty_response()
Beispiel #16
0
    def put(self, user_id: str, **kwargs: Any) -> Response:

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

        if user is None:
            raise NotFound(
                "This user cannot be found or you are not authorized")

        if "password" in kwargs:
            unhashed_password = kwargs["password"]
            kwargs["password"] = BaseAuthentication.get_password_hash(
                kwargs["password"])
        else:
            unhashed_password = None

        payload = kwargs.copy()
        roles: List[str] = kwargs.pop("roles", [])

        group_id = kwargs.pop("group", None)

        email_notification = kwargs.pop("email_notification", False)

        self.auth.link_roles(user, roles)

        userdata, extra_userdata = self.auth.custom_user_properties_pre(kwargs)

        prev_expiration = user.expiration

        self.auth.db.update_properties(user, userdata)

        self.auth.custom_user_properties_post(user, userdata, extra_userdata,
                                              self.auth.db)

        self.auth.save_user(user)

        if group_id is not None:
            group = self.auth.get_group(group_id=group_id)
            if not group:
                # Can't be reached because grup_id is prefiltered by marshmallow
                raise NotFound(
                    "This group cannot be found")  # pragma: no cover

            self.auth.add_user_to_group(user, group)

        if email_notification and unhashed_password is not None:
            smtp_client = smtp.get_instance()
            send_notification(smtp_client,
                              user,
                              unhashed_password,
                              is_update=True)

        if user.expiration:
            # Set expiration on a previously non-expiring account
            # or update the expiration by reducing the validity period
            # In both cases tokens should be invalited to prevent to have tokens
            # with TTL > account validity

            # dt_lower (alias for date_lower_than) is a comparison fn that ignores tz
            if prev_expiration is None or dt_lower(user.expiration,
                                                   prev_expiration):
                for token in self.auth.get_tokens(user=user):
                    # Invalidate all tokens with expiration after the account expiration
                    if dt_lower(user.expiration, token["expiration"]):
                        self.auth.invalidate_token(token=token["token"])

        self.log_event(self.events.modify, user, payload)

        return self.empty_response()
Beispiel #17
0
    def put(self, user_id=None):

        if user_id is None:

            raise RestApiException(
                "Please specify a user id",
                status_code=hcodes.HTTP_BAD_REQUEST)

        if not detector.check_availability('neo4j'):
            log.warning("This endpoint is implemented only for neo4j")
            return self.empty_response()

        schema = self.get_endpoint_custom_definition()
        self.graph = self.get_service_instance('neo4j')

        is_admin = self.auth.verify_admin()
        is_group_admin = self.auth.verify_group_admin()
        if not is_admin and not is_group_admin:
            raise RestApiException(
                "You are not authorized: missing privileges",
                status_code=hcodes.HTTP_BAD_UNAUTHORIZED)

        v = self.get_input()

        user = self.graph.User.nodes.get_or_none(uuid=user_id)
        # user = self.getNode(self.graph.User, user_id, field='uuid')
        if user is None:
            raise RestApiException(
                "This user cannot be found or you are not authorized")

        current_user = self.get_current_user()
        is_authorized = self.check_permissions(
            current_user, user, is_admin, is_group_admin
        )
        if not is_authorized:
            raise RestApiException(
                "This user cannot be found or you are not authorized")

        unhashed_password = None
        if "password" in v and v["password"] == "":
            del v["password"]
        else:
            v["password"] = BaseAuthentication.hash_password(v["password"])

        if "email" in v:
            v["email"] = v["email"].lower()

        self.update_properties(user, schema, v)
        user.save()

        if 'group' in v:

            group = self.parse_group(v)

            if not is_admin:
                if not group.coordinator.is_connected(current_user):
                    raise RestApiException(
                        "You are allowed to assign users to this group")

            p = None
            for p in user.belongs_to.all():
                if p == group:
                    continue

            if p is not None:
                user.belongs_to.reconnect(p, group)
            else:
                user.belongs_to.connect(group)

        roles = self.parse_roles(v)
        if not is_admin:
            allowed_roles = mem.customizer._configurations \
                .get('variables', {}) \
                .get('backend', {}) \
                .get('allowed_roles', [])

            for r in roles:
                if r not in allowed_roles:
                    raise RestApiException(
                        "You are allowed to assign users to this role")

        self.auth.link_roles(user, roles)

        email_notification = v.get('email_notification', False)
        if email_notification and unhashed_password is not None:
            self.send_notification(user, unhashed_password, is_update=True)

        return self.empty_response()
Beispiel #18
0
    def test_authentication_abstract_methods(faker: Faker) -> None:

        # Super trick!
        # https://clamytoe.github.io/articles/2020/Mar/12/testing-abcs-with-abstract-methods-with-pytest
        abstractmethods = BaseAuthentication.__abstractmethods__
        BaseAuthentication.__abstractmethods__ = frozenset()

        auth = Connector.get_authentication_instance()
        user = auth.get_user(username=BaseAuthentication.default_user)
        group = auth.get_group(name=DEFAULT_GROUP_NAME)
        role = auth.get_roles()[0]

        auth = BaseAuthentication()  # type: ignore

        assert (auth.get_user(username=faker.ascii_email(),
                              user_id=faker.pystr()) is None)

        assert auth.get_users() is None
        assert auth.save_user(user=user) is None
        assert auth.delete_user(user=user) is None

        assert auth.get_group(group_id=faker.pystr(),
                              name=faker.pystr()) is None

        assert auth.get_groups() is None
        assert auth.get_user_group(user=user) is None

        assert auth.get_group_members(group=group) is None

        assert auth.save_group(group=group) is None

        assert auth.delete_group(group=group) is None

        assert auth.get_tokens(
            user=user, token_jti=faker.pystr(), get_all=True) is None

        assert auth.verify_token_validity(jti=faker.pystr(), user=user) is None

        assert (auth.save_token(user=user,
                                token=faker.pystr(),
                                payload={},
                                token_type=faker.pystr()) is None)

        assert auth.invalidate_token(token=faker.pystr()) is None

        assert auth.get_roles() is None

        assert auth.get_roles_from_user(user=user) is None

        assert auth.create_role(name=faker.pystr(),
                                description=faker.pystr()) is None
        assert auth.save_role(role=role) is None

        assert auth.create_user(userdata={}, roles=[faker.pystr()]) is None

        assert auth.link_roles(user=user, roles=[faker.pystr()]) is None
        assert auth.create_group(groupdata={}) is None

        assert auth.add_user_to_group(user=user, group=group) is None

        assert (auth.save_login(
            username=faker.ascii_email(), user=user, failed=True) is None)
        assert (auth.save_login(
            username=faker.ascii_email(), user=None, failed=True) is None)
        assert (auth.save_login(
            username=faker.ascii_email(), user=user, failed=False) is None)
        assert (auth.save_login(
            username=faker.ascii_email(), user=None, failed=False) is None)

        assert auth.get_logins(username=faker.ascii_email) is None

        assert auth.flush_failed_logins(username=faker.ascii_email) is None

        BaseAuthentication.__abstractmethods__ = abstractmethods
Beispiel #19
0
    def put(self, user_id=None):

        if user_id is None:

            raise RestApiException("Please specify a user id",
                                   status_code=hcodes.HTTP_BAD_REQUEST)

        schema = self.get_endpoint_custom_definition()
        if self.neo4j_enabled:
            self.graph = self.get_service_instance('neo4j')

        is_admin = self.auth.verify_admin()
        is_local_admin = self.auth.verify_local_admin()
        if not is_admin and not is_local_admin:
            raise RestApiException(
                "You are not authorized: missing privileges",
                status_code=hcodes.HTTP_BAD_UNAUTHORIZED,
            )

        v = self.get_input()

        user = self.auth.get_users(user_id)

        if user is None:
            raise RestApiException(
                "This user cannot be found or you are not authorized")

        user = user[0]

        current_user = self.get_current_user()
        is_authorized = self.check_permissions(current_user, user, is_admin,
                                               is_local_admin)
        if not is_authorized:
            raise RestApiException(
                "This user cannot be found or you are not authorized")

        if "password" in v and v["password"] == "":
            del v["password"]

        if "password" in v:
            unhashed_password = v["password"]
            v["password"] = BaseAuthentication.get_password_hash(v["password"])
        else:
            unhashed_password = None

        if "email" in v:
            v["email"] = v["email"].lower()

        roles = self.parse_roles(v)
        if not is_admin:
            allowed_roles = get_project_configuration(
                "variables.backend.allowed_roles",
                default=[],
            )

            for r in roles:
                if r not in allowed_roles:
                    raise RestApiException(
                        "You are not allowed to assign users to this role")

        self.auth.link_roles(user, roles)
        # Cannot update email address (unique username used to login-in)
        v.pop('email', None)

        if self.neo4j_enabled:
            self.update_properties(user, schema, v)
        elif self.sql_enabled:
            self.update_sql_properties(user, schema, v)
        elif self.mongo_enabled:
            self.update_mongo_properties(user, schema, v)
        else:
            raise RestApiException(
                "Invalid auth backend, all known db are disabled")

        self.auth.save_user(user)

        # FIXME: groups management is only implemented for neo4j
        if 'group' in v:

            group = self.parse_group(v)

            if not is_admin and group.shortname != "default":
                if not group.coordinator.is_connected(current_user):
                    raise RestApiException(
                        "You are not allowed to assign users to this group")

            p = None
            for p in user.belongs_to.all():
                if p == group:
                    continue

            if p is not None:
                user.belongs_to.reconnect(p, group)
            else:
                user.belongs_to.connect(group)

        email_notification = v.get('email_notification', False)
        if email_notification and unhashed_password is not None:
            self.send_notification(user, unhashed_password, is_update=True)

        return self.empty_response()
Beispiel #20
0
        assert r.status_code == 200
        assert content is not None

        headers: CaseInsensitiveDict[str] = CaseInsensitiveDict()
        headers["Authorization"] = f"Bearer {content}"
        return content, headers

    # No need to restore the logger after this test because
    # schemathesis test is the last one!
    # (just because in alphabetic order there are no other tests)
    set_logger("WARNING")
    app = create_app()
    client = werkzeug.Client(app, werkzeug.wrappers.Response)

    if Env.get_bool("AUTH_ENABLE"):
        BaseAuthentication.load_default_user()
        BaseAuthentication.load_roles()
        USER = BaseAuthentication.default_user
        PWD = BaseAuthentication.default_password
        data = {"username": USER, "password": PWD}
        token, auth_header = get_auth_token(client, data)

        # it does not handle custom headers => the endpoint will provide partial schema
        # due to missing authentication => skipping all private endpoints and schemas
        # schema = schemathesis.from_wsgi('/api/specs', app)
        r = client.get(f"/api/specs?access_token={token}")
    else:
        r = client.get("/api/specs")

    assert r.status_code == 200
    schema = orjson.loads(r.get_data().decode())
Beispiel #21
0
    def do_login(
        cls,
        client: FlaskClient,
        USER: Optional[str],
        PWD: Optional[str],
        status_code: int = 200,
        data: Optional[Dict[str, Any]] = None,
        test_failures: bool = False,
    ) -> Tuple[Optional[Dict[str, str]], str]:
        """
        Make login and return both token and authorization header
        """

        if USER is None or PWD is None:
            BaseAuthentication.load_default_user()
            BaseAuthentication.load_roles()
        if USER is None:
            USER = BaseAuthentication.default_user
        if PWD is None:
            PWD = BaseAuthentication.default_password

        assert USER is not None
        assert PWD is not None

        if data is None:
            data = {}

        data["username"] = USER
        data["password"] = PWD

        r = client.post(f"{AUTH_URI}/login", data=data)
        content = json.loads(r.data.decode("utf-8"))

        if r.status_code == 403:

            # This 403 is expected, return an invalid value or you can enter a loop!
            if status_code == 403:
                return None, content

            if isinstance(content, dict) and content.get("actions"):
                actions = content.get("actions", [])

                for action in actions:
                    if action == "TOTP":
                        continue
                    if action == "FIRST LOGIN":
                        continue
                    if action == "PASSWORD EXPIRED":
                        continue

                data = {}

                if "FIRST LOGIN" in actions or "PASSWORD EXPIRED" in actions:
                    newpwd = cls.faker.password(strong=True)
                    if test_failures:
                        data["new_password"] = newpwd
                        data["password_confirm"] = cls.faker.password(
                            strong=True)
                        if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"):
                            data["totp_code"] = BaseTests.generate_totp(USER)

                        BaseTests.do_login(
                            client,
                            USER,
                            PWD,
                            data=data,
                            status_code=409,
                        )

                        # Test failure of password change if TOTP is wrong or missing
                        if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"):
                            data["new_password"] = newpwd
                            data["password_confirm"] = newpwd
                            data.pop("totp_code", None)

                            BaseTests.do_login(
                                client,
                                USER,
                                PWD,
                                data=data,
                                status_code=403,
                            )

                            data["new_password"] = newpwd
                            data["password_confirm"] = newpwd
                            # random int with 6 digits
                            data["totp_code"] = cls.faker.pyint(
                                min_value=100000, max_value=999999)
                            BaseTests.do_login(
                                client,
                                USER,
                                PWD,
                                data=data,
                                status_code=401,
                            )

                    # Change the password to silence FIRST_LOGIN and PASSWORD_EXPIRED
                    data["new_password"] = newpwd
                    data["password_confirm"] = newpwd
                    if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"):
                        data["totp_code"] = BaseTests.generate_totp(USER)
                    BaseTests.do_login(
                        client,
                        USER,
                        PWD,
                        data=data,
                    )
                    # Change again to restore the default password
                    # and keep all other tests fully working
                    data["new_password"] = PWD
                    data["password_confirm"] = PWD
                    if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"):
                        data["totp_code"] = BaseTests.generate_totp(USER)
                    return BaseTests.do_login(
                        client,
                        USER,
                        newpwd,
                        data=data,
                    )

                # in this case FIRST LOGIN has not been executed
                # => login by sending the TOTP code
                if "TOTP" in actions:
                    # Only directly tested => no coverage
                    if test_failures:  # pragma: no cover
                        # random int with 6 digits
                        data["totp_code"] = cls.faker.pyint(min_value=100000,
                                                            max_value=999999)
                        BaseTests.do_login(
                            client,
                            USER,
                            PWD,
                            data=data,
                            status_code=401,
                        )

                    data["totp_code"] = BaseTests.generate_totp(USER)
                    return BaseTests.do_login(
                        client,
                        USER,
                        PWD,
                        data=data,
                    )

        # FOR DEBUGGING WHEN ADVANCED AUTH OPTIONS ARE ON
        # if r.status_code != 200:
        #     c = json.loads(r.data.decode("utf-8"))
        #     log.error(c)

        assert r.status_code == status_code

        # when 200 OK content is the token
        assert content is not None

        return {"Authorization": f"Bearer {content}"}, content