コード例 #1
0
ファイル: __init__.py プロジェクト: fossabot/http-api
    def wrapper(self, *args, **kwargs):

        try:
            return func(self, *args, **kwargs)

        except BaseException:

            task_id = self.request.id
            task_name = self.request.task

            log.error("Celery task {} failed ({})", task_id, task_name)
            arguments = str(self.request.args)
            log.error("Failed task arguments: {}", arguments[0:256])
            log.error("Task error: {}", traceback.format_exc())

            if send_mail_is_active():
                log.info("Sending error report by email", task_id, task_name)

                body = """
Celery task {} failed

Name: {}

Arguments: {}

Error: {}
""".format(task_id, task_name, str(self.request.args), traceback.format_exc())

                project = get_project_configuration(
                    "project.title",
                    default='Unkown title',
                )
                subject = "{}: task {} failed".format(project, task_name)
                send_mail(body, subject)
コード例 #2
0
ファイル: admin_users.py プロジェクト: fossabot/http-api
    def post(self):

        v = self.get_input()
        if len(v) == 0:
            raise RestApiException('Empty input',
                                   status_code=hcodes.HTTP_BAD_REQUEST)

        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,
            )

        schema = self.get_endpoint_custom_definition()

        if 'get_schema' in v:

            new_schema = schema[:]

            if send_mail_is_active():
                new_schema.append({
                    "name": "email_notification",
                    "description": "Notify password by email",
                    "type": "boolean",
                    "default": False,
                    "custom": {
                        "htmltype": "checkbox",
                        "label": "Notify password by email",
                    },
                })

            if 'autocomplete' in v and not v['autocomplete']:
                for idx, val in enumerate(new_schema):
                    # FIXME: groups management is only implemented for neo4j
                    if val["name"] == "group":
                        new_schema[idx]["default"] = None

                        if "custom" not in new_schema[idx]:
                            new_schema[idx]["custom"] = {}

                        new_schema[idx]["custom"]["htmltype"] = "select"
                        new_schema[idx]["custom"]["label"] = "Group"
                        new_schema[idx]["enum"] = []

                        for g in self.graph.Group.nodes.all():
                            group_name = "{} - {}".format(
                                g.shortname, g.fullname)
                            new_schema[idx]["enum"].append(
                                {g.uuid: group_name})
                            if new_schema[idx]["default"] is None:
                                new_schema[idx]["default"] = g.uuid

                    # Roles as multi checkbox
                    if val["name"] == "roles":

                        roles = self.auth.get_roles()
                        is_admin = self.auth.verify_admin()
                        allowed_roles = get_project_configuration(
                            "variables.backend.allowed_roles",
                            default=[],
                        )
                        del new_schema[idx]

                        for r in roles:

                            if is_admin:
                                if r.description == 'automatic':
                                    continue
                            else:
                                if r.name not in allowed_roles:
                                    continue

                            role = {
                                "type": "checkbox",
                                "name": "roles_{}".format(r.name),
                                "custom": {
                                    "label": r.description
                                },
                            }

                            new_schema.insert(idx, role)

            if is_admin:
                return self.force_response(new_schema)

            current_user = self.get_current_user()
            for idx, val in enumerate(new_schema):
                # FIXME: groups management is only implemented for neo4j
                if val["name"] == "group":
                    new_schema[idx]["default"] = None
                    if "custom" not in new_schema[idx]:
                        new_schema[idx]["custom"] = {}

                    new_schema[idx]["custom"]["htmltype"] = "select"
                    new_schema[idx]["custom"]["label"] = "Group"
                    new_schema[idx]["enum"] = []

                    default_group = self.graph.Group.nodes.get_or_none(
                        shortname="default")

                    defg = None
                    if default_group is not None:
                        new_schema[idx]["enum"].append(
                            {default_group.uuid: default_group.shortname})
                        # new_schema[idx]["default"] = default_group.uuid
                        defg = default_group.uuid

                    for g in current_user.coordinator.all():

                        if g == default_group:
                            continue

                        group_name = "{} - {}".format(g.shortname, g.fullname)
                        new_schema[idx]["enum"].append({g.uuid: group_name})
                        if defg is None:
                            defg = g.uuid
                        # if new_schema[idx]["default"] is None:
                        #     new_schema[idx]["default"] = g.uuid
                    if (len(new_schema[idx]["enum"])) == 1:
                        new_schema[idx]["default"] = defg

            return self.force_response(new_schema)

        # INIT #
        properties = self.read_properties(schema, v)

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

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

        if "password" in properties:
            unhashed_password = properties["password"]
        else:
            unhashed_password = None

        try:
            user = self.auth.create_user(properties, roles)
        except AttributeError as e:

            # Duplicated from decorators
            prefix = "Can't create user .*:\nNode\([0-9]+\) already exists with label"
            m = re.search(
                "{} `(.+)` and property `(.+)` = '(.+)'".format(prefix),
                str(e))

            if m:
                node = m.group(1)
                prop = m.group(2)
                val = m.group(3)
                error = "A {} already exists with {} = {}".format(
                    node, prop, val)
                raise RestApiException(error,
                                       status_code=hcodes.HTTP_BAD_CONFLICT)
            else:
                raise e

        if self.sql_enabled:

            try:
                self.auth.db.session.commit()
            except IntegrityError:
                self.auth.db.session.rollback()
                raise RestApiException("This user already exists")

        # If created by admins, credentials
        # must accept privacy at the login
        if "privacy_accepted" in v:
            if not v["privacy_accepted"]:
                if hasattr(user, 'privacy_accepted'):
                    user.privacy_accepted = False
                    self.auth.save_user(user)

        # FIXME: groups management is only implemented for neo4j
        group = None
        if 'group' in v:
            group = self.parse_group(v)

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

            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=False)

        return self.force_response(user.uuid)
コード例 #3
0
ファイル: endpoints.py プロジェクト: beetleman/http-api
    def post(self):

        if not send_mail_is_active():
            raise RestApiException(
                'Server misconfiguration, unable to reset password. ' +
                'Please report this error to adminstrators',
                status_code=hcodes.HTTP_BAD_REQUEST)

        reset_email = self.get_input(single_parameter='reset_email')

        if reset_email is None:
            raise RestApiException(
                'Invalid reset email',
                status_code=hcodes.HTTP_BAD_FORBIDDEN)

        reset_email = reset_email.lower()

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

        if user is None:
            raise RestApiException(
                'Sorry, %s ' % reset_email +
                'is not recognized as a valid username or email address',
                status_code=hcodes.HTTP_BAD_FORBIDDEN)

        title = mem.customizer._configurations \
            .get('project', {}) \
            .get('title', "Unkown title")

        # invalidate previous reset tokens
        tokens = self.auth.get_tokens(user=user)
        for t in tokens:
            token_type = t.get("token_type")
            if token_type is None:
                continue
            if token_type != self.auth.PWD_RESET:
                continue

            tok = t.get("token")
            if self.auth.invalidate_token(tok):
                log.info("Previous reset token invalidated: %s", tok)

        # Generate a new reset token
        reset_token, jti = self.auth.create_temporary_token(
            user,
            duration=86400,
            token_type=self.auth.PWD_RESET
        )

        domain = os.environ.get("DOMAIN")
        if PRODUCTION:
            protocol = "https"
        else:
            protocol = "http"

        u = "%s://%s/public/reset/%s" % (protocol, domain, reset_token)
        body = "link to reset password: %s" % u

        replaces = {
            "url": u
        }
        html_body = get_html_template("reset_password.html", replaces)
        # html_body = "link to reset password: <a href='%s'>click here</a>" % u
        subject = "%s Password Reset" % title
        send_mail(html_body, subject, reset_email, plain_body=body)

        self.auth.save_token(
            user, reset_token, jti, token_type=self.auth.PWD_RESET)

        msg = "We are sending an email to your email address where " + \
            "you will find the link to enter a new password"
        return msg
コード例 #4
0
ファイル: admin_users.py プロジェクト: beetleman/http-api
    def post(self):

        v = self.get_input()
        if len(v) == 0:
            raise RestApiException(
                'Empty input',
                status_code=hcodes.HTTP_BAD_REQUEST)

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

        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)

        schema = self.get_endpoint_custom_definition()

        if 'get_schema' in v:

            new_schema = schema[:]

            if send_mail_is_active():
                new_schema.append(
                    {
                        "name": "email_notification",
                        "description": "Notify password by email",
                        "type": "boolean",
                        "default": False,
                        "custom": {
                            "htmltype": "checkbox",
                            "label": "Notify password by email"
                        }
                    }
                )
            if is_admin:
                return self.force_response(new_schema)

            # institutes = self.graph.Institute.nodes
            # users = self.graph.User.nodes
            current_user = self.get_current_user()
            for idx, val in enumerate(new_schema):
                if val["name"] == "group":
                    new_schema[idx]["default"] = None
                    new_schema[idx]["custom"] = {
                        "htmltype": "select",
                        "label": "Group"
                    }
                    new_schema[idx]["enum"] = []

                    for g in current_user.coordinator.all():
                        new_schema[idx]["enum"].append(
                            {g.uuid: g.shortname}
                        )
                        if new_schema[idx]["default"] is None:
                            new_schema[idx]["default"] = g.uuid

            return self.force_response(new_schema)
 
        # INIT #
        properties = self.read_properties(schema, v)

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

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

        if "password" in properties:
            unhashed_password = properties["password"]
        else:
            unhashed_password = None

        user = self.auth.create_user(properties, roles)

        group = None
        if 'group' in v:
            group = self.parse_group(v)

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

            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=False)

        return self.force_response(user.uuid)
コード例 #5
0
    def post(self):

        v = self.get_input()
        if len(v) == 0:
            raise RestApiException('Empty input',
                                   status_code=hcodes.HTTP_BAD_REQUEST)

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

        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)

        schema = self.get_endpoint_custom_definition()

        if 'get_schema' in v:

            new_schema = schema[:]

            if send_mail_is_active():
                new_schema.append({
                    "name": "email_notification",
                    "description": "Notify password by email",
                    "type": "boolean",
                    "default": False,
                    "custom": {
                        "htmltype": "checkbox",
                        "label": "Notify password by email"
                    }
                })

            if 'autocomplete' in v and not v['autocomplete']:
                for idx, val in enumerate(new_schema):
                    if val["name"] == "group":
                        new_schema[idx]["default"] = None

                        if "custom" not in new_schema[idx]:
                            new_schema[idx]["custom"] = {}

                        new_schema[idx]["custom"]["htmltype"]: "select"
                        new_schema[idx]["custom"]["label"]: "Group"

                        new_schema[idx]["enum"] = []

                        for g in self.graph.Group.nodes.all():
                            new_schema[idx]["enum"].append(
                                {g.uuid: g.fullname})
                            if new_schema[idx]["default"] is None:
                                new_schema[idx]["default"] = g.uuid

                    # Roles as multi checkbox
                    if val["name"] == "roles":

                        cypher = "MATCH (r:Role)"
                        if not self.auth.verify_admin():
                            allowed_roles = mem.customizer._configurations \
                                .get('variables', {}) \
                                .get('backend', {}) \
                                .get('allowed_roles', [])
                            cypher += " WHERE r.name in %s" % allowed_roles
                        # Admin only
                        else:
                            cypher += " WHERE r.description <> 'automatic'"

                        cypher += " RETURN r ORDER BY r.name ASC"

                        result = self.graph.cypher(cypher)

                        del new_schema[idx]

                        for row in result:
                            r = self.graph.Role.inflate(row[0])

                            role = {
                                "type": "checkbox",
                                # "name": "roles[%s]" % r.name,
                                "name": "roles_%s" % r.name,
                                # "name": r.name,
                                "custom": {
                                    "label": r.description
                                }
                            }

                            new_schema.insert(idx, role)

            if is_admin:
                return self.force_response(new_schema)

            # institutes = self.graph.Institute.nodes
            # users = self.graph.User.nodes
            current_user = self.get_current_user()
            for idx, val in enumerate(new_schema):
                if val["name"] == "group":
                    new_schema[idx]["default"] = None
                    new_schema[idx]["custom"] = {
                        "htmltype": "select",
                        "label": "Group"
                    }
                    new_schema[idx]["enum"] = []

                    default_group = self.graph.Group.nodes.get_or_none(
                        shortname="default")

                    if default_group is not None:
                        new_schema[idx]["enum"].append(
                            {default_group.uuid: default_group.shortname})
                        new_schema[idx]["default"] = default_group.uuid

                    for g in current_user.coordinator.all():

                        if g == default_group:
                            continue

                        new_schema[idx]["enum"].append({g.uuid: g.shortname})
                        if new_schema[idx]["default"] is None:
                            new_schema[idx]["default"] = g.uuid

            return self.force_response(new_schema)

        # INIT #
        properties = self.read_properties(schema, v)

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

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

        if "password" in properties:
            unhashed_password = properties["password"]
        else:
            unhashed_password = None

        user = self.auth.create_user(properties, roles)

        group = None
        if 'group' in v:
            group = self.parse_group(v)

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

            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=False)

        return self.force_response(user.uuid)
コード例 #6
0
ファイル: profile.py プロジェクト: fossabot/http-api
    def post(self):

        if not send_mail_is_active():
            raise RestApiException(
                'Server misconfiguration, unable to reset password. '
                + 'Please report this error to adminstrators',
                status_code=hcodes.HTTP_BAD_REQUEST,
            )

        reset_email = self.get_input(single_parameter='reset_email')

        if reset_email is None:
            raise RestApiException(
                'Invalid reset email', status_code=hcodes.HTTP_BAD_FORBIDDEN
            )

        reset_email = reset_email.lower()

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

        if user is None:
            raise RestApiException(
                'Sorry, {} is not recognized as a valid username'.format(reset_email),
                status_code=hcodes.HTTP_BAD_FORBIDDEN,
            )

        if user.is_active is not None and not user.is_active:
            # Beware, frontend leverages on this exact message,
            # do not modified it without fix also on frontend side
            raise RestApiException(
                "Sorry, this account is not active",
                status_code=hcodes.HTTP_BAD_UNAUTHORIZED,
            )

        title = get_project_configuration(
            "project.title", default='Unkown title'
        )

        reset_token, jti = self.auth.create_reset_token(user, self.auth.PWD_RESET)

        domain = os.environ.get("DOMAIN")
        if PRODUCTION:
            protocol = "https"
        else:
            protocol = "http"

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

        var = "RESET_PASSWORD_URI"
        uri = detector.get_global_var(key=var, default='/public/reset')
        complete_uri = "{}://{}{}/{}".format(protocol, domain, uri, rt)

        ##################
        # Send email with internal or external SMTP
        obj = meta.get_customizer_class('apis.profile', 'CustomReset')
        if obj is None:
            # normal activation + internal smtp
            send_internal_password_reset(complete_uri, title, reset_email)
        else:
            # external smtp
            obj.request_reset(user.name, user.email, complete_uri)

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

        msg = "You will receive an email shortly with a link to a page where you can create a new password, please check your spam/junk folder."

        return self.force_response(msg)
コード例 #7
0
ファイル: profile.py プロジェクト: fossabot/http-api
    def post(self):
        """ Register new user """

        if not send_mail_is_active():
            raise RestApiException(
                'Server misconfiguration, unable to reset password. '
                + 'Please report this error to adminstrators',
                status_code=hcodes.HTTP_BAD_REQUEST,
            )

        v = self.get_input()
        if len(v) == 0:
            raise RestApiException('Empty input', status_code=hcodes.HTTP_BAD_REQUEST)

        # INIT #
        # schema = self.get_endpoint_custom_definition()
        # properties = self.read_properties(schema, v)

        if 'password' not in v:
            raise RestApiException(
                "Missing input: password", status_code=hcodes.HTTP_BAD_REQUEST
            )

        if 'email' not in v:
            raise RestApiException(
                "Missing input: email", status_code=hcodes.HTTP_BAD_REQUEST
            )

        if 'name' not in v:
            raise RestApiException(
                "Missing input: name", status_code=hcodes.HTTP_BAD_REQUEST
            )

        if 'surname' not in v:
            raise RestApiException(
                "Missing input: surname", status_code=hcodes.HTTP_BAD_REQUEST
            )

        user = self.auth.get_user_object(username=v['email'])
        if user is not None:
            raise RestApiException(
                "This user already exists: {}".format(v['email']),
                status_code=hcodes.HTTP_BAD_REQUEST,
            )

        v['is_active'] = False
        user = self.auth.create_user(v, [self.auth.default_role])

        try:
            self.auth.custom_post_handle_user_input(user, v)
            send_activation_link(self.auth, user)
            notify_registration(user)
            msg = (
                "We are sending an email to your email address where "
                + "you will find the link to activate your account"
            )

        except BaseException as e:
            log.error("Errors during account registration: {}", str(e))
            user.delete()
            raise RestApiException(str(e))
        else:
            custom_extra_registration(v)
            return self.force_response(msg)
コード例 #8
0
def create_app(
    name=__name__,
    init_mode=False,
    destroy_mode=False,
    worker_mode=False,
    testing_mode=False,
    skip_endpoint_mapping=False,
    **kwargs,
):
    """ Create the server istance for Flask application """

    if PRODUCTION and testing_mode:
        log.exit("Unable to execute tests in production")

    # Initialize reading of all files
    mem.customizer = Customizer(testing_mode, init_mode)
    mem.geo_reader = geolite2.reader()
    # when to close??
    # geolite2.close()

    # Add template dir for output in HTML
    kwargs['template_folder'] = os.path.join(ABS_RESTAPI_PATH, 'templates')

    # Flask app instance
    microservice = Flask(name, **kwargs)

    # Add commands to 'flask' binary
    if init_mode:
        microservice.config['INIT_MODE'] = init_mode
        skip_endpoint_mapping = True
    elif destroy_mode:
        microservice.config['DESTROY_MODE'] = destroy_mode
        skip_endpoint_mapping = True
    elif testing_mode:
        microservice.config['TESTING'] = testing_mode
        init_mode = True
    elif worker_mode:
        skip_endpoint_mapping = True

    # Fix proxy wsgi for production calls
    microservice.wsgi_app = ProxyFix(microservice.wsgi_app)

    # CORS
    if not PRODUCTION:
        cors = CORS(
            allow_headers=[
                'Content-Type', 'Authorization', 'X-Requested-With',
                'x-upload-content-length', 'x-upload-content-type',
                'content-range'
            ],
            supports_credentials=['true'],
            methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
        )

        cors.init_app(microservice)
        log.verbose("FLASKING! Injected CORS")

    # Enabling our internal Flask customized response
    microservice.response_class = InternalResponse

    # Flask configuration from config file
    microservice.config.from_object(config)
    log.debug("Flask app configured")

    if PRODUCTION:
        log.info("Production server mode is ON")

    # Find services and try to connect to the ones available
    extensions = detector.init_services(
        app=microservice,
        worker_mode=worker_mode,
        project_init=init_mode,
        project_clean=destroy_mode,
    )

    if worker_mode:
        microservice.extensions = extensions

    # Restful plugin
    if not skip_endpoint_mapping:
        # Triggering automatic mapping of REST endpoints
        rest_api = Api(catch_all_404s=True)

        # Basic configuration (simple): from example class
        if len(mem.customizer._endpoints) < 1:
            log.error("No endpoints found!")

            raise AttributeError("Follow the docs and define your endpoints")

        for resource in mem.customizer._endpoints:
            # urls = [uri for _, uri in resource.uris.items()]
            urls = list(resource.uris.values())

            # Create the restful resource with it;
            # this method is from RESTful plugin
            rest_api.add_resource(resource.cls, *urls)

            log.verbose("Map '{}' to {}", resource.cls.__name__, urls)

        # Enable all schema endpoints to be mapped with this extra step
        if len(mem.customizer._schema_endpoint.uris) > 0:
            log.debug("Found one or more schema to expose")
            urls = [
                uri for _, uri in mem.customizer._schema_endpoint.uris.items()
            ]
            rest_api.add_resource(mem.customizer._schema_endpoint.cls, *urls)

        # HERE all endpoints will be registered by using FlaskRestful
        rest_api.init_app(microservice)

        microservice.services_instances = {}
        for m in detector.services_classes:
            ExtClass = detector.services_classes.get(m)
            microservice.services_instances[m] = ExtClass(microservice)

        # FlaskApiSpec experimentation
        from apispec import APISpec
        from flask_apispec import FlaskApiSpec
        from apispec.ext.marshmallow import MarshmallowPlugin
        # from apispec_webframeworks.flask import FlaskPlugin

        microservice.config.update({
            'APISPEC_SPEC':
            APISpec(
                title=glom(mem.customizer._configurations,
                           'project.title',
                           default='0.0.1'),
                version=glom(mem.customizer._configurations,
                             'project.version',
                             default='Your application name'),
                openapi_version="2.0",
                # OpenApi 3 not working with FlaskApiSpec
                # -> Duplicate parameter with name body and location body
                # https://github.com/jmcarp/flask-apispec/issues/170
                # Find other warning like this by searching:
                # **FASTAPI**
                # openapi_version="3.0.2",
                plugins=[MarshmallowPlugin()],
            ),
            'APISPEC_SWAGGER_URL':
            '/api/swagger',
            # 'APISPEC_SWAGGER_UI_URL': '/api/swagger-ui',
            # Disable Swagger-UI
            'APISPEC_SWAGGER_UI_URL':
            None,
        })
        docs = FlaskApiSpec(microservice)

        with microservice.app_context():
            for resource in mem.customizer._endpoints:
                urls = list(resource.uris.values())
                try:
                    docs.register(resource.cls)
                except TypeError as e:
                    # log.warning("{} on {}", type(e), resource.cls)
                    # Enable this warning to start conversion to FlaskFastApi
                    # Find other warning like this by searching:
                    # **FASTAPI**
                    log.verbose("{} on {}", type(e), resource.cls)

    # Clean app routes
    ignore_verbs = {"HEAD", "OPTIONS"}

    for rule in microservice.url_map.iter_rules():

        rulename = str(rule)
        # Skip rules that are only exposing schemas
        if '/schemas/' in rulename:
            continue

        endpoint = microservice.view_functions[rule.endpoint]
        if not hasattr(endpoint, 'view_class'):
            continue
        newmethods = ignore_verbs.copy()

        for verb in rule.methods - ignore_verbs:
            method = verb.lower()
            if method in mem.customizer._original_paths[rulename]:
                # remove from flask mapping
                # to allow 405 response
                newmethods.add(verb)
            else:
                log.verbose("Removed method {}.{} from mapping", rulename,
                            verb)

        rule.methods = newmethods

    # Logging responses
    @microservice.after_request
    def log_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)
        # NOTE: if it is an upload,
        # I must NOT consume request.data or request.json,
        # otherwise the content gets lost
        do_not_log_types = ['application/octet-stream', 'multipart/form-data']

        if request.mimetype in do_not_log_types:
            data = 'STREAM_UPLOAD'
        else:
            try:
                data = handle_log_output(request.data)
                # Limit the parameters string size, sometimes it's too big
                for k in data:
                    try:
                        if isinstance(data[k], dict):
                            for kk in data[k]:
                                v = str(data[k][kk])
                                if len(v) > MAX_CHAR_LEN:
                                    v = v[:MAX_CHAR_LEN] + "..."
                                data[k][kk] = v
                            continue

                        if not isinstance(data[k], str):
                            data[k] = str(data[k])

                        if len(data[k]) > MAX_CHAR_LEN:
                            data[k] = data[k][:MAX_CHAR_LEN] + "..."
                    except IndexError:
                        pass
            except Exception:
                data = 'OTHER_UPLOAD'

        # Obfuscating query parameters
        url = urllib_parse.urlparse(request.url)
        try:
            params = urllib_parse.unquote(
                urllib_parse.urlencode(handle_log_output(url.query)))
            url = url._replace(query=params)
        except TypeError:
            log.error("Unable to url encode the following parameters:")
            print(url.query)

        url = urllib_parse.urlunparse(url)
        log.info("{} {} {} {}", request.method, url, data, response)

        return response

    if send_mail_is_active():
        if not test_smtp_client():
            log.critical("Bad SMTP configuration, unable to create a client")
        else:
            log.info("SMTP configuration verified")
    # and the flask App is ready now:
    log.info("Boot completed")

    if SENTRY_URL is not None:

        if not PRODUCTION:
            log.info("Skipping Sentry, only enabled in PRODUCTION mode")
        else:
            import sentry_sdk
            from sentry_sdk.integrations.flask import FlaskIntegration

            sentry_sdk.init(dsn=SENTRY_URL, integrations=[FlaskIntegration()])
            log.info("Enabled Sentry {}", SENTRY_URL)

    # return our flask app
    return microservice