Exemple #1
0
    def get(self, query=None):

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

        data = []

        cypher = "MATCH (r:Role)"
        if not self.auth.verify_admin():
            allowed_roles = get_project_configuration(
                "variables.backend.allowed_roles",
                default=[],
            )
            # cypher += " WHERE r.name = 'Archive' or r.name = 'Researcher'"
            cypher += " WHERE r.name in {}".format(allowed_roles)
        # Admin only
        elif query is not None:
            cypher += " WHERE r.description <> 'automatic'"
            cypher += " AND r.name =~ '(?i).*{}.*'".format(query)

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

        if query is None:
            cypher += " LIMIT 20"

        result = self.graph.cypher(cypher)
        for row in result:
            r = self.graph.Role.inflate(row[0])
            data.append({"name": r.name, "description": r.description})

        return self.force_response(data)
Exemple #2
0
    def myinit(cls):

        credentials = get_project_configuration(
            "variables.backend.credentials")

        if credentials.get('username') is not None:
            log.exit("Obsolete use of variables.backend.credentials.username")

        if credentials.get('password') is not None:
            log.exit("Obsolete use of variables.backend.credentials.password")

        # cls.default_user = credentials.get('username', None)
        # cls.default_password = credentials.get('password', None)
        cls.default_user = Detector.get_global_var('AUTH_DEFAULT_USERNAME')
        cls.default_password = Detector.get_global_var('AUTH_DEFAULT_PASSWORD')
        if cls.default_user is None or cls.default_password is None:
            log.exit("Default credentials are unavailable!")

        roles = credentials.get('roles', {})
        cls.default_role = roles.get('default')
        cls.role_admin = roles.get('admin')
        cls.default_roles = [
            roles.get('user'),
            roles.get('internal'), cls.role_admin
        ]
        if cls.default_role is None or None in cls.default_roles:
            log.exit("Default roles are not available!")
Exemple #3
0
    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)
Exemple #4
0
    def send_notification(self, user, unhashed_password, is_update=False):

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

        if is_update:
            subject = "{}: password changed".format(title)
            template = "update_credentials.html"
        else:
            subject = "{}: new credentials".format(title)
            template = "new_credentials.html"

        replaces = {"username": user.email, "password": unhashed_password}

        html = get_html_template(template, replaces)

        body = """
Username: "******"
Password: "******"
        """.format(
            user.email,
            unhashed_password,
        )

        if html is None:
            send_mail(body, subject, user.email)
        else:
            send_mail(html, subject, user.email, plain_body=body)
Exemple #5
0
def notify_registration(user):
    var = "REGISTRATION_NOTIFICATIONS"
    if detector.get_bool_from_os(var):
        # Sending an email to the administrator
        title = get_project_configuration(
            "project.title", default='Unkown title'
        )
        subject = "{} New credentials requested".format(title)
        body = "New credentials request from {}".format(user.email)

        send_mail(body, subject)
Exemple #6
0
    def get_qrcode(self, user):

        secret = self.get_secret(user)
        totp = pyotp.TOTP(secret)

        project_name = get_project_configuration('project.title', "No project name")

        otpauth_url = totp.provisioning_uri(project_name)
        qr_url = pyqrcode.create(otpauth_url)
        qr_stream = BytesIO()
        qr_url.svg(qr_stream, scale=5)
        return qr_stream.getvalue()
Exemple #7
0
    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
Exemple #8
0
def send_activation_link(auth, user):

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

    activation_token, jti = auth.create_reset_token(user, auth.ACTIVATE_ACCOUNT)

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

    rt = activation_token.replace(".", "+")
    log.debug("Activation token: {}", rt)
    url = "{}://{}/public/register/{}".format(protocol, domain, rt)
    body = "Follow this link to activate your account: {}".format(url)

    obj = meta.get_customizer_class('apis.profile', 'CustomActivation')

    # NORMAL ACTIVATION
    if obj is None:

        # customized template
        template_file = "activate_account.html"
        html_body = get_html_template(template_file, {"url": url})
        if html_body is None:
            html_body = body
            body = None

        # NOTE: possibility to define a different subject
        default_subject = "{} account activation".format(title)
        subject = os.environ.get('EMAIL_ACTIVATION_SUBJECT', default_subject)

        sent = send_mail(html_body, subject, user.email, plain_body=body)
        if not sent:
            raise BaseException("Error sending email, please retry")

    # EXTERNAL SMTP/EMAIL SENDER
    else:
        try:
            obj.request_activation(name=user.name, email=user.email, url=url)
        except BaseException as e:
            log.error(
                "Could not send email with custom service:\n{}: {}",
                e.__class__.__name__,
                e,
            )
            raise

    auth.save_token(user, activation_token, jti, token_type=auth.ACTIVATE_ACCOUNT)
Exemple #9
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()
Exemple #10
0
    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)
Exemple #11
0
    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)