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