def do_login(form): email = form.get("email", "").lower() password = form.get("password") next_url = form.get("next", "") res = dict(username=email, email=email, next_url=next_url) if not email or not password: res["error"] = _("You must provide your email and password.") res["code"] = 401 return res try: user = User.query.filter( sql.func.lower(User.email) == email, User.can_login == True ).one() except NoResultFound: auth_failed.send(unwrap(current_app), email=email) res["error"] = _( "Sorry, we couldn't find an account for " "email '{email}'." ).format(email=email) res["code"] = 401 return res if user and not user.authenticate(password): auth_failed.send(unwrap(current_app), email=email) res["error"] = _("Sorry, wrong password.") res["code"] = 401 return res # Login successful login_user(user) res["user"] = user res["email"] = user.email return res
def do_login(form): email = form.get("email", "").lower() password = form.get("password") next_url = form.get("next", "") res = {"username": email, "email": email, "next_url": next_url} if not email or not password: res["error"] = _("You must provide your email and password.") res["code"] = 401 return res try: user = User.query.filter( sql.func.lower(User.email) == email, User.can_login == True ).one() except NoResultFound: auth_failed.send(unwrap(current_app), email=email) res["error"] = _( "Sorry, we couldn't find an account for " "email '{email}'." ).format(email=email) res["code"] = 401 return res if user and not user.authenticate(password): auth_failed.send(unwrap(current_app), email=email) res["error"] = _("Sorry, wrong password.") res["code"] = 401 return res # Login successful login_user(user) res["user"] = user res["email"] = user.email return res
def filter_with_permission(self, user, permission, obj_list, inherit=False): user = unwrap(user) return [ obj for obj in obj_list if self.has_permission(user, permission, obj, inherit) ]
def filter_with_permission(self, user, permission, obj_list, inherit=False): user = unwrap(user) return [ obj for obj in obj_list if self.has_permission(user, permission, obj, inherit) ]
def create_document(folder, fs): check_write_access(folder) if isinstance(fs.filename, str): name = fs.filename else: name = str(fs.filename, errors="ignore") if not name: flash(_("Document name can't be empty."), "error") return None original_name = name name = get_new_filename(folder, name) doc = folder.create_document(title=name) doc.set_content(fs.read(), fs.content_type) if original_name != name: # set message after document has been successfully created! flash( _('"{original}" already present in folder, ' 'renamed "{name}"').format( original=original_name, name=name ), "info", ) # Some unwrapping before posting event app = unwrap(current_app) community = g.community._model activity.send(app, actor=current_user, verb="post", object=doc, target=community) return doc
def create_document(folder, fs): check_write_access(folder) if isinstance(fs.filename, text_type): name = fs.filename else: name = text_type(fs.filename, errors="ignore") if not name: flash(_("Document name can't be empty."), "error") return None original_name = name name = get_new_filename(folder, name) doc = folder.create_document(title=name) doc.set_content(fs.read(), fs.content_type) if original_name != name: # set message after document has been successfully created! flash( _('"{original}" already present in folder, ' 'renamed "{name}"').format(original=original_name, name=name), "info", ) # Some unwrapping before posting event app = unwrap(current_app) community = g.community._model activity.send(app, actor=current_user, verb="post", object=doc, target=community) return doc
def wizard_saving(): """Automatically add existing accounts to the current community. Create accounts for new emails, add them to the community and send them a password reset email. """ community = g.community._model existing_accounts = request.form.get("existing_account") existing_accounts = json.loads(existing_accounts) new_accounts = request.form.get("new_accounts") new_accounts = json.loads(new_accounts) if not (existing_accounts or new_accounts): flash(_("No new members were found"), "warning") return redirect(url_for(".members", community_id=g.community.slug)) if existing_accounts: for email, role in existing_accounts.items(): user = User.query.filter(User.email == email).first() community.set_membership(user, role) app = unwrap(current_app) activity.send(app, actor=user, verb="join", object=community) db.session.commit() if new_accounts: for account in new_accounts: email = account["email"] first_name = account["first_name"] last_name = account["last_name"] role = account["role"] user = User( email=email, last_name=last_name, first_name=first_name, can_login=True ) db.session.add(user) community.set_membership(user, role) app = unwrap(current_app) activity.send(app, actor=user, verb="join", object=community) db.session.commit() send_reset_password_instructions(user) flash(_("New members added successfully"), "success") return redirect(url_for(".members", community_id=community.slug))
def wizard_saving(): """Automatically add existing accounts to the current community. Create accounts for new emails, add them to the community and send them a password reset email. """ community = g.community._model existing_accounts = request.form.get("existing_account") existing_accounts = json.loads(existing_accounts) new_accounts = request.form.get("new_accounts") new_accounts = json.loads(new_accounts) if not (existing_accounts or new_accounts): flash(_("No new members were found"), "warning") return redirect(url_for(".members", community_id=g.community.slug)) if existing_accounts: for email, role in existing_accounts.items(): user = User.query.filter(User.email == email).first() community.set_membership(user, role) app = unwrap(current_app) activity.send(app, actor=user, verb="join", object=community) db.session.commit() if new_accounts: for account in new_accounts: email = account["email"] first_name = account["first_name"] last_name = account["last_name"] role = account["role"] user = User( email=email, last_name=last_name, first_name=first_name, can_login=True ) db.session.add(user) community.set_membership(user, role) app = unwrap(current_app) activity.send(app, actor=user, verb="join", object=community) db.session.commit() send_reset_password_instructions(user) flash(_("New members added successfully"), "success") return redirect(url_for(".members", community_id=community.slug))
def grant_role(self, principal, role, obj=None): """Grant `role` to `user` (either globally, if `obj` is None, or on the specific `obj`).""" assert principal principal = unwrap(principal) session = object_session(obj) if obj is not None else db.session manager = self._current_user_manager(session=session) args = { "role": role, "object": obj, "anonymous": False, "user": None, "group": None, } if principal is AnonymousRole or ( hasattr(principal, "is_anonymous") and principal.is_anonymous ): args["anonymous"] = True elif isinstance(principal, User): args["user"] = principal else: args["group"] = principal query = session.query(RoleAssignment) if query.filter_by(**args).limit(1).count(): # role already granted, nothing to do return # same as above but in current, not yet flushed objects in session. We # cannot call flush() in grant_role() since this method may be called a # great number of times in the same transaction, and sqlalchemy limits # to 100 flushes before triggering a warning for ra in ( o for models in (session.new, session.dirty) for o in models if isinstance(o, RoleAssignment) ): if all(getattr(ra, attr) == val for attr, val in args.items()): return ra = RoleAssignment(**args) session.add(ra) audit = SecurityAudit(manager=manager, op=SecurityAudit.GRANT, **args) if obj is not None: audit.object_id = obj.id audit.object_type = obj.entity_type object_name = "" for attr_name in ("name", "path", "__path_before_delete"): if hasattr(obj, attr_name): object_name = getattr(obj, attr_name) audit.object_name = object_name session.add(audit) self._needs_flush() if hasattr(principal, "__roles_cache__"): del principal.__roles_cache__
def grant_role(self, principal, role, obj=None): """Grant `role` to `user` (either globally, if `obj` is None, or on the specific `obj`).""" assert principal principal = unwrap(principal) session = object_session(obj) if obj is not None else db.session manager = self._current_user_manager(session=session) args = { "role": role, "object": obj, "anonymous": False, "user": None, "group": None, } if principal is AnonymousRole or ( hasattr(principal, "is_anonymous") and principal.is_anonymous ): args["anonymous"] = True elif isinstance(principal, User): args["user"] = principal else: args["group"] = principal query = session.query(RoleAssignment) if query.filter_by(**args).limit(1).count(): # role already granted, nothing to do return # same as above but in current, not yet flushed objects in session. We # cannot call flush() in grant_role() since this method may be called a # great number of times in the same transaction, and sqlalchemy limits # to 100 flushes before triggering a warning for ra in ( o for models in (session.new, session.dirty) for o in models if isinstance(o, RoleAssignment) ): if all(getattr(ra, attr) == val for attr, val in args.items()): return ra = RoleAssignment(**args) session.add(ra) audit = SecurityAudit(manager=manager, op=SecurityAudit.GRANT, **args) if obj is not None: audit.object_id = obj.id audit.object_type = obj.entity_type object_name = "" for attr_name in ("name", "path", "__path_before_delete"): if hasattr(obj, attr_name): object_name = getattr(obj, attr_name) audit.object_name = object_name session.add(audit) self._needs_flush() if hasattr(principal, "__roles_cache__"): del principal.__roles_cache__
def finalize_validate(): config_file = Path(current_app.instance_path) / "config.py" logging_file = Path(current_app.instance_path) / "logging.yml" assert not config_file.exists() config = cmd_config.DefaultConfig(logging_file="logging.yml") config.SQLALCHEMY_DATABASE_URI = session_get("db")["uri"] redis_uri = session_get("redis")["uri"] config.REDIS_URI = redis_uri config.BROKER_URL = redis_uri config.CELERY_RESULT_BACKEND = redis_uri d = session_get("site_info") config.SITE_NAME = d["sitename"] config.MAIL_SENDER = d["mailsender"] is_production = d["server_mode"] == "production" config.PRODUCTION = is_production config.DEBUG = not is_production config.DEBUG_TB_ENABLED = config.DEBUG config.CELERY_ALWAYS_EAGER = not is_production cmd_config.write_config(config_file, config) cmd_config.maybe_write_logging(logging_file) admin_account = session_get("admin_account") # create a new app that will be configured with new config, # to create database and admin_user setup_app = unwrap(current_app) app = setup_app.__class__( setup_app.import_name, static_url_path=setup_app.static_url_path, static_folder=setup_app.static_folder, template_folder=setup_app.template_folder, instance_path=setup_app.instance_path, ) with app.test_request_context("/setup/finalize"): app.create_db() db_session = db.session() admin = User( email=admin_account["email"], password=admin_account["password"], last_name=admin_account["name"], first_name=admin_account["firstname"], can_login=True, ) db_session.add(admin) security = get_service("security") security.grant_role(admin, Admin) db_session.commit() session_clear() return render_template("setupwizard/done.html", config_file=config_file, logging_file=logging_file)
def finalize_validate(): config_file = Path(current_app.instance_path) / "config.py" logging_file = Path(current_app.instance_path) / "logging.yml" assert not config_file.exists() config = cmd_config.DefaultConfig(logging_file="logging.yml") config.SQLALCHEMY_DATABASE_URI = session_get("db")["uri"] redis_uri = session_get("redis")["uri"] config.REDIS_URI = redis_uri config.BROKER_URL = redis_uri config.CELERY_RESULT_BACKEND = redis_uri d = session_get("site_info") config.SITE_NAME = d["sitename"] config.MAIL_SENDER = d["mailsender"] is_production = d["server_mode"] == "production" config.PRODUCTION = is_production config.DEBUG = not is_production config.DEBUG_TB_ENABLED = config.DEBUG config.CELERY_ALWAYS_EAGER = not is_production cmd_config.write_config(config_file, config) cmd_config.maybe_write_logging(logging_file) admin_account = session_get("admin_account") # create a new app that will be configured with new config, # to create database and admin_user setup_app = unwrap(current_app) app = setup_app.__class__( setup_app.import_name, static_url_path=setup_app.static_url_path, static_folder=setup_app.static_folder, template_folder=setup_app.template_folder, instance_path=setup_app.instance_path, ) with app.test_request_context("/setup/finalize"): app.create_db() db_session = db.session() admin = User( email=admin_account["email"], password=admin_account["password"], last_name=admin_account["name"], first_name=admin_account["firstname"], can_login=True, ) db_session.add(admin) security = get_service("security") security.grant_role(admin, Admin) db_session.commit() session_clear() return render_template( "setupwizard/done.html", config_file=config_file, logging_file=logging_file )
def flask_app(self) -> Flask: if has_app_context(): return unwrap(flask_current_app) self.flask_app_factory = symbol_by_name(self.flask_app_factory) app = self.flask_app_factory() register_after_fork(app, self._setup_after_fork) return app
def filter_with_permission( self, user: User, permission: Union[Permission, str], obj_list: List[Model], inherit=False, ): user = unwrap(user) return [ obj for obj in obj_list if self.has_permission(user, permission, obj, inherit) ]
def members_post(): community = g.community._model action = request.form.get("action") user_id = request.form.get("user") if not user_id: flash(_("You must provide a user."), "error") return redirect(url_for(".members", community_id=community.slug)) user_id = int(user_id) user = User.query.get(user_id) if action in ("add-user-role", "set-user-role"): role = request.form.get("role").lower() community.set_membership(user, role) if action == "add-user-role": app = unwrap(current_app) activity.send(app, actor=user, verb="join", object=community) db.session.commit() return redirect(url_for(".members", community_id=community.slug)) elif action == "delete": membership_id = int(request.form["membership"]) membership = Membership.query.get(membership_id) if membership.user_id != user_id: raise InternalServerError() community.remove_membership(user) app = unwrap(current_app) activity.send(app, actor=user, verb="leave", object=community) db.session.commit() return redirect(url_for(".members", community_id=community.slug)) else: raise BadRequest("Unknown action: {}".format(repr(action)))
def document_upload(doc_id): doc = get_document(doc_id) check_write_access(doc) fd = request.files["file"] doc.set_content(fd.read(), fd.content_type) del doc.lock self = unwrap(current_app) activity.send(self, actor=current_user, verb="update", object=doc) db.session.commit() flash(_("New version successfully uploaded"), "success") return redirect(url_for(doc))
def document_upload(doc_id): doc = get_document(doc_id) check_write_access(doc) fd = request.files["file"] doc.set_content(fd.read(), fd.content_type) del doc.lock self = unwrap(current_app) activity.send(self, actor=current_user, verb="update", object=doc) db.session.commit() flash(_("New version successfully uploaded"), "success") return redirect(url_for(doc))
def members_post(): community = g.community._model action = request.form.get("action") user_id = request.form.get("user") if not user_id: flash(_("You must provide a user."), "error") return redirect(url_for(".members", community_id=community.slug)) user_id = int(user_id) user = User.query.get(user_id) if action in ("add-user-role", "set-user-role"): role = request.form.get("role").lower() community.set_membership(user, role) if action == "add-user-role": app = unwrap(current_app) activity.send(app, actor=user, verb="join", object=community) db.session.commit() return redirect(url_for(".members", community_id=community.slug)) elif action == "delete": membership_id = int(request.form["membership"]) membership = Membership.query.get(membership_id) if membership.user_id != user_id: raise InternalServerError() community.remove_membership(user) app = unwrap(current_app) activity.send(app, actor=user, verb="leave", object=community) db.session.commit() return redirect(url_for(".members", community_id=community.slug)) else: raise BadRequest("Unknown action: {}".format(repr(action)))
def do_access_control(self) -> Union[None, Response]: """`before_request` handler to check if user should be redirected to login page.""" from abilian.services import get_service if current_app.testing and current_app.config.get("NO_LOGIN"): # Special case for tests user = User.query.get(0) login_user(user, force=True) return None state = self.app_state user = unwrap(current_user) # Another special case for tests if current_app.testing and getattr(user, "is_admin", False): return None # pyre-fixme[9]: security has type `SecurityService`; used as `Service`. security: SecurityService = get_service("security") user_roles = frozenset(security.get_roles(user)) endpoint = request.endpoint blueprint = request.blueprint access_controllers: List[Callable] = [] access_controllers.extend(state.bp_access_controllers.get(None, [])) if blueprint and blueprint in state.bp_access_controllers: access_controllers.extend(state.bp_access_controllers[blueprint]) if endpoint and endpoint in state.endpoint_access_controllers: access_controllers.extend( state.endpoint_access_controllers[endpoint]) for access_controller in reversed(access_controllers): verdict = access_controller(user=user, roles=user_roles) if verdict is None: continue elif verdict is True: return None else: if user.is_anonymous: return self.redirect_to_login() raise Forbidden() # default policy if current_app.config.get("PRIVATE_SITE") and user.is_anonymous: return self.redirect_to_login() return None
def ungrant_role( self, principal: Principal, role: Union[Role, str], object: Optional[Model] = None, ) -> None: """Ungrant `role` to `user` (either globally, if `object` is None, or on the specific `object`).""" assert principal principal = unwrap(principal) session = object_session(object) if object is not None else db.session manager = self._current_user_manager(session=session) args = { "role": role, "object": object, "anonymous": False, "user": None, "group": None, } query = session.query(RoleAssignment) query = query.filter( RoleAssignment.role == role, RoleAssignment.object == object ) if principal is AnonymousRole or ( hasattr(principal, "is_anonymous") and principal.is_anonymous ): args["anonymous"] = True query.filter( RoleAssignment.anonymous == False, RoleAssignment.user == None, RoleAssignment.group == None, ) elif isinstance(principal, User): args["user"] = principal query = query.filter(RoleAssignment.user == principal) else: args["group"] = principal query = query.filter(RoleAssignment.group == principal) ra = query.one() session.delete(ra) audit = SecurityAudit(manager=manager, op=SecurityAudit.REVOKE, **args) session.add(audit) self._needs_flush() self._clear_role_cache(principal)
def flask_app(self): if has_app_context(): return unwrap(flask_current_app) self.flask_app_factory = symbol_by_name(self.flask_app_factory) app = self.flask_app_factory() if "sentry" in app.extensions: from raven.contrib.celery import register_signal, register_logger_signal client = app.extensions["sentry"].client client.tags["process_type"] = "celery task" register_signal(client) register_logger_signal(client) register_after_fork(app, self._setup_after_fork) return app
def page_delete(): title = request.form["title"].strip() try: page = get_page_by_title(title) except NoResultFound: flash(_("This page doesn't exist"), "error") return redirect(url_for(".index", community_id=g.community.slug)) db.session.delete(page) app = unwrap(current_app) community = g.community._model activity.send(app, actor=current_user, verb="delete", object=page, target=community) db.session.commit() flash(_("Page %(title)s deleted.", title=title)) return redirect(url_for(".index", community_id=g.community.slug))
def do_access_control(self): """`before_request` handler to check if user should be redirected to login page.""" from abilian.services import get_service if current_app.testing and current_app.config.get("NO_LOGIN"): # Special case for tests user = User.query.get(0) login_user(user, force=True) return state = self.app_state user = unwrap(current_user) # Another special case for tests if current_app.testing and getattr(user, "is_admin", False): return security = get_service("security") user_roles = frozenset(security.get_roles(user)) endpoint = request.endpoint blueprint = request.blueprint access_controllers = [] access_controllers.extend(state.bp_access_controllers.get(None, [])) if blueprint and blueprint in state.bp_access_controllers: access_controllers.extend(state.bp_access_controllers[blueprint]) if endpoint and endpoint in state.endpoint_access_controllers: access_controllers.extend(state.endpoint_access_controllers[endpoint]) for access_controller in reversed(access_controllers): verdict = access_controller(user=user, roles=user_roles) if verdict is None: continue elif verdict is True: return else: if user.is_anonymous: return self.redirect_to_login() raise Forbidden() # default policy if current_app.config.get("PRIVATE_SITE") and user.is_anonymous: return self.redirect_to_login()
def flask_app(self): if has_app_context(): return unwrap(flask_current_app) self.flask_app_factory = symbol_by_name(self.flask_app_factory) app = self.flask_app_factory() if "sentry" in app.extensions: from raven.contrib.celery import register_signal, register_logger_signal client = app.extensions["sentry"].client client.tags["process_type"] = "celery task" register_signal(client) register_logger_signal(client) register_after_fork(app, self._setup_after_fork) return app
def before_request(self): req = unwrap(request) failed = req.csrf_failed if not failed: return rule = req.url_rule view = current_app.view_functions[rule.endpoint] if getattr(view, "csrf_support_graceful_failure", False): # view can handle it nicely for the user return None if hasattr(view, "view_class") and getattr( view.view_class, "csrf_support_graceful_failure", False): return None raise BadRequest(failed)
def ungrant_role(self, principal, role, object=None): """Ungrant `role` to `user` (either globally, if `object` is None, or on the specific `object`).""" assert principal principal = unwrap(principal) session = object_session(object) if object is not None else db.session manager = self._current_user_manager(session=session) args = { "role": role, "object": object, "anonymous": False, "user": None, "group": None, } query = session.query(RoleAssignment) query = query.filter( RoleAssignment.role == role, RoleAssignment.object == object ) if principal is AnonymousRole or ( hasattr(principal, "is_anonymous") and principal.is_anonymous ): args["anonymous"] = True query.filter( RoleAssignment.anonymous == False, RoleAssignment.user == None, RoleAssignment.group == None, ) elif isinstance(principal, User): args["user"] = principal query = query.filter(RoleAssignment.user == principal) else: args["group"] = principal query = query.filter(RoleAssignment.group == principal) ra = query.one() session.delete(ra) audit = SecurityAudit(manager=manager, op=SecurityAudit.REVOKE, **args) session.add(audit) self._needs_flush() self._clear_role_cache(principal)
def flask_app(self) -> Flask: if has_app_context(): return unwrap(flask_current_app) self.flask_app_factory = symbol_by_name(self.flask_app_factory) app = self.flask_app_factory() if "sentry" in app.extensions: # pyre-fixme[21]: Could not find `raven`. from raven.contrib.celery import register_signal, register_logger_signal client = app.extensions["sentry"].client client.tags["process_type"] = "celery task" register_signal(client) register_logger_signal(client) # pyre-fixme[16]: Module `multiprocessing` has no attribute `util`. register_after_fork(app, self._setup_after_fork) return app
def before_request(self): req = unwrap(request) failed = req.csrf_failed if not failed: return rule = req.url_rule view = current_app.view_functions[rule.endpoint] if getattr(view, "csrf_support_graceful_failure", False): # view can handle it nicely for the user return None if hasattr(view, "view_class") and getattr( view.view_class, "csrf_support_graceful_failure", False ): return None raise BadRequest(failed)
def delete_multiple(folder): check_write_access(folder) folders, docs = get_selected_objects(folder) for obj in docs + folders: app = unwrap(current_app) community = g.community._model activity.send(app, actor=current_user, verb="delete", object=obj, target=community) repository.delete_object(obj) if docs + folders: db.session.commit() if docs and folders: msg = _( "%(file_num)d files and %(folder_num)d folders sucessfully " "deleted.", file_num=len(docs), folder_num=len(folders), ) elif docs and not folders: msg = _n( "1 file sucessfully deleted.", "%(num)d files sucessfully deleted.", num=len(docs), ) else: msg = _n( "1 folder sucessfully deleted.", "%(num)d folders sucessfully deleted.", num=len(folders), ) flash(msg, "success") else: flash(_("No object deleted"), "error") return redirect(url_for(folder))
def page_delete(): title = request.form["title"].strip() try: page = get_page_by_title(title) except NoResultFound: flash(_("This page doesn't exist"), "error") return redirect(url_for(".index", community_id=g.community.slug)) db.session.delete(page) app = unwrap(current_app) community = g.community._model activity.send(app, actor=current_user, verb="delete", object=page, target=community) db.session.commit() flash(_("Page %(title)s deleted.", title=title)) return redirect(url_for(".index", community_id=g.community.slug))
def load_user(user_id: str) -> Optional[User]: try: user = User.query.get(user_id) if not user or not user.can_login: # if a user is edited and should not have access any more, this # will ensure they cannot continue if he had an active session return None except Exception: logger.warning("Error during login.", exc_info=True) session = db.session() if not session.is_active: # session is not usable, rollback should restore a usable # session session.rollback() return None app = unwrap(current_app) app.services[AuthService.name].user_logged_in(app, user) user_loaded.send(app, user=user) return user
def delete_multiple(folder): check_write_access(folder) folders, docs = get_selected_objects(folder) for obj in docs + folders: app = unwrap(current_app) community = g.community._model activity.send( app, actor=current_user, verb="delete", object=obj, target=community ) repository.delete_object(obj) if docs + folders: db.session.commit() if docs and folders: msg = _( "%(file_num)d files and %(folder_num)d folders sucessfully " "deleted.", file_num=len(docs), folder_num=len(folders), ) elif docs and not folders: msg = _n( "1 file sucessfully deleted.", "%(num)d files sucessfully deleted.", num=len(docs), ) else: msg = _n( "1 folder sucessfully deleted.", "%(num)d folders sucessfully deleted.", num=len(folders), ) flash(msg, "success") else: flash(_("No object deleted"), "error") return redirect(url_for(folder))
def load_user(user_id): try: user = User.query.get(user_id) if not user or not user.can_login: # if a user is edited and should not have access any more, this # will ensure they cannot continue if he had an active session return None except Exception: logger.warning("Error during login.", exc_info=True) session = db.session() if not session.is_active: # session is not usable, rollback should restore a usable # session session.rollback() return None app = unwrap(current_app) app.services[AuthService.name].user_logged_in(app, user) user_loaded.send(app, user=user) return user
def indexable_role(principal): """Return a string suitable for query against `allowed_roles_and_users` field. :param principal: It can be :data:`Anonymous`, :data:`Authenticated`, or an instance of :class:`User` or :class:`Group`. """ principal = unwrap(principal) if hasattr(principal, "is_anonymous") and principal.is_anonymous: # transform anonymous user to anonymous role principal = Anonymous if isinstance(principal, Role): return "role:{}".format(principal.name) elif isinstance(principal, User): fmt = "user:{:d}" elif isinstance(principal, Group): fmt = "group:{:d}" else: raise ValueError(repr(principal)) return fmt.format(principal.id)
def indexable_role(principal): """Return a string suitable for query against `allowed_roles_and_users` field. :param principal: It can be :data:`Anonymous`, :data:`Authenticated`, or an instance of :class:`User` or :class:`Group`. """ principal = unwrap(principal) if hasattr(principal, "is_anonymous") and principal.is_anonymous: # transform anonymous user to anonymous role principal = Anonymous if isinstance(principal, Role): return f"role:{principal.name}" elif isinstance(principal, User): fmt = "user:{:d}" elif isinstance(principal, Group): fmt = "group:{:d}" else: raise ValueError(repr(principal)) return fmt.format(principal.id)
def check_security() -> None: user = unwrap(current_user) if not security.has_role(user, "admin"): raise Forbidden()
def check_security(): user = unwrap(current_user) if not security.has_role(user, "admin"): raise Forbidden()
def query_entity_with_permission(self, permission, user=None, Model=Entity): """Filter a query on an :class:`Entity` or on of its subclasses. Usage:: read_q = security.query_entity_with_permission(READ, Model=MyModel) MyModel.query.filter(read_q) It should always be placed before any `.join()` happens in the query; else sqlalchemy might join to the "wrong" entity table when joining to other :class:`Entity`. :param user: user to filter for. Default: `current_user`. :param permission: required :class:`Permission` :param Model: An :class:`Entity` based class. Useful when there is more than one Entity based object in query, or if an alias should be used. :returns: a `sqlalchemy.sql.exists()` expression. """ assert isinstance(permission, Permission) assert issubclass(Model, Entity) RA = sa.orm.aliased(RoleAssignment) PA = sa.orm.aliased(PermissionAssignment) # id column from entity table. Model.id would refer to 'model' table. # this allows the DB to use indexes / foreign key constraints. id_column = sa.inspect(Model).primary_key[0] creator = Model.creator owner = Model.owner if not self.running: return sa.sql.exists([1]) if user is None: user = unwrap(current_user) # build role CTE principal_filter = RA.anonymous == True if not user.is_anonymous: principal_filter |= RA.user == user if user.groups: principal_filter |= RA.group_id.in_([g.id for g in user.groups]) RA = sa.sql.select([RA], principal_filter).cte() permission_exists = sa.sql.exists([1]).where( sa.sql.and_( PA.permission == permission, PA.object_id == id_column, (RA.c.role == PA.role) | (PA.role == AnonymousRole), (RA.c.object_id == PA.object_id) | (RA.c.object_id == None), ) ) # is_admin: self-explanatory. It search for local or global admin # role, but PermissionAssignment is not involved, thus it can match on # entities that don't have *any permission assignment*, whereas previous # expressions cannot. is_admin = sa.sql.exists([1]).where( sa.sql.and_( RA.c.role == Admin, (RA.c.object_id == id_column) | (RA.c.object_id == None), principal_filter, ) ) filter_expr = permission_exists | is_admin if user and not user.is_anonymous: is_owner_or_creator = sa.sql.exists([1]).where( sa.sql.and_( PA.permission == permission, PA.object_id == id_column, sa.sql.or_( (PA.role == Owner) & (owner == user), (PA.role == Creator) & (creator == user), ), ) ) filter_expr |= is_owner_or_creator return filter_expr
def has_permission(self, user, permission, obj=None, inherit=False, roles=None): """ :param obj: target object to check permissions. :param inherit: check with permission inheritance. By default, check only local roles. :param roles: additional valid role or iterable of roles having `permission`. """ if not isinstance(permission, Permission): assert permission in PERMISSIONS permission = Permission(permission) user = unwrap(user) if not self.running: return True session = None if obj is not None: session = object_session(obj) if session is None: session = db.session() # root always have any permission if isinstance(user, User) and user.id == 0: return True # valid roles # 1: from database pa_filter = PermissionAssignment.object == None if obj is not None and obj.id is not None: pa_filter |= PermissionAssignment.object == obj pa_filter &= PermissionAssignment.permission == permission valid_roles = session.query(PermissionAssignment.role).filter(pa_filter) valid_roles = {res[0] for res in valid_roles.yield_per(1000)} # complete with defaults valid_roles |= {Admin} # always have all permissions valid_roles |= DEFAULT_PERMISSION_ROLE.get(permission, set()) # FIXME: obj.__class__ could define default permisssion matrix too if roles is not None: if isinstance(roles, (Role,) + (str,)): roles = (roles,) for r in roles: valid_roles.add(Role(r)) # FIXME: query permission_role: global and on object if AnonymousRole in valid_roles: return True if Authenticated in valid_roles and not user.is_anonymous: return True # first test global roles, then object local roles checked_objs = [None, obj] if inherit and obj is not None: while obj.inherit_security and obj.parent is not None: obj = obj.parent checked_objs.append(obj) principals = [user] + list(user.groups) self._fill_role_cache_batch(principals) return any( self.has_role(principal, valid_roles, item) for principal in principals for item in checked_objs )
def prepare_args(self, args, kwargs): args, kwargs = super().prepare_args(args, kwargs) self.uploads = current_app.extensions["uploads"] self.user = unwrap(current_user) return args, kwargs
def process_email(message): # type: (email.message.Message) -> bool """Email.Message object from command line script Run message (parsed email). Processing chain extract community thread post member from reply_to persist post in db. """ app = unwrap(current_app) # Extract post destination from To: field, (community/forum/thread/member) to_address = message["To"] assert isinstance(to_address, text_type) if not (has_subtag(to_address)): logger.info("Email %r has no subtag, skipping...", to_address) return False try: infos = extract_email_destination(to_address) locale = infos[0] thread_id = infos[1] user_id = infos[2] except BaseException: logger.error( "Recipient %r cannot be converted to locale/thread_id/user.id", to_address, exc_info=True, ) return False # Translate marker with locale from email address rq_headers = [("Accept-Language", locale)] with app.test_request_context("/process_email", headers=rq_headers): marker = text_type(MAIL_REPLY_MARKER) # Extract text and attachments from message try: newpost, attachments = process(message, marker) except BaseException: logger.error("Could not Process message", exc_info=True) return False # Persist post with current_app.test_request_context("/process_email", headers=rq_headers): g.user = User.query.get(user_id) thread = Thread.query.get(thread_id) community = thread.community # FIXME: check membership, send back an informative email in case of an # error post = thread.create_post(body_html=newpost) obj_meta = post.meta.setdefault("abilian.sbe.forum", {}) obj_meta["origin"] = "email" obj_meta["send_by_email"] = True activity.send(app, actor=g.user, verb="post", object=post, target=community) for desc in attachments: attachment = PostAttachment(name=desc["filename"]) attachment.post = post attachment.set_content(desc["data"], desc["content_type"]) db.session.add(attachment) db.session.commit() # Notify all parties involved send_post_by_email.delay(post.id) return True
def has_role(self, principal, role, object=None): """True if `principal` has `role` (either globally, if `object` is None, or on the specific `object`). :param:role: can be a list or tuple of strings or a :class:`Role` instance `object` can be an :class:`Entity`, a string, or `None`. Note: we're using a cache for efficiency here. TODO: check that we're not over-caching. Note2: caching could also be moved upfront to when the user is loaded. """ if not principal: return False principal = unwrap(principal) if not self.running: return True if isinstance(role, (Role, (str,))): role = (role,) # admin & manager always have role valid_roles = frozenset((Admin, Manager) + tuple(role)) if AnonymousRole in valid_roles: # everybody has the role 'Anonymous' return True if ( Authenticated in valid_roles and isinstance(principal, User) and not principal.is_anonymous ): return True if principal is AnonymousRole or ( hasattr(principal, "is_anonymous") and principal.is_anonymous ): # anonymous user, and anonymous role isn't in valid_roles return False # root always have any role if isinstance(principal, User) and principal.id == 0: return True if object: assert isinstance(object, Entity) object_key = "{}:{}".format(object.object_type, str(object.id)) if Creator in role: if object.creator == principal: return True if Owner in role: if object.owner == principal: return True else: object_key = None all_roles = ( self._fill_role_cache(principal) if self.app_state.use_cache else self._all_roles(principal) ) roles = set() roles |= all_roles.get(None, set()) roles |= all_roles.get(object_key, set()) return len(valid_roles & roles) > 0
def prepare_args(self, args, kwargs): args, kwargs = super().prepare_args(args, kwargs) self.uploads = current_app.extensions["uploads"] self.user = unwrap(current_user) return args, kwargs
def process_email(message): # type: (email.message.Message) -> bool """Email.Message object from command line script Run message (parsed email). Processing chain extract community thread post member from reply_to persist post in db. """ app = unwrap(current_app) # Extract post destination from To: field, (community/forum/thread/member) to_address = message["To"] assert isinstance(to_address, str) if not (has_subtag(to_address)): logger.info("Email %r has no subtag, skipping...", to_address) return False try: infos = extract_email_destination(to_address) locale = infos[0] thread_id = infos[1] user_id = infos[2] except BaseException: logger.error( "Recipient %r cannot be converted to locale/thread_id/user.id", to_address, exc_info=True, ) return False # Translate marker with locale from email address rq_headers = [("Accept-Language", locale)] with app.test_request_context("/process_email", headers=rq_headers): marker = str(MAIL_REPLY_MARKER) # Extract text and attachments from message try: newpost, attachments = process(message, marker) except BaseException: logger.error("Could not Process message", exc_info=True) return False # Persist post with current_app.test_request_context("/process_email", headers=rq_headers): g.user = User.query.get(user_id) thread = Thread.query.get(thread_id) community = thread.community # FIXME: check membership, send back an informative email in case of an # error post = thread.create_post(body_html=newpost) obj_meta = post.meta.setdefault("abilian.sbe.forum", {}) obj_meta["origin"] = "email" obj_meta["send_by_email"] = True activity.send(app, actor=g.user, verb="post", object=post, target=community) for desc in attachments: attachment = PostAttachment(name=desc["filename"]) attachment.post = post attachment.set_content(desc["data"], desc["content_type"]) db.session.add(attachment) db.session.commit() # Notify all parties involved send_post_by_email.delay(post.id) return True
def has_role(self, principal, role, object=None): """True if `principal` has `role` (either globally, if `object` is None, or on the specific `object`). :param:role: can be a list or tuple of strings or a :class:`Role` instance `object` can be an :class:`Entity`, a string, or `None`. Note: we're using a cache for efficiency here. TODO: check that we're not over-caching. Note2: caching could also be moved upfront to when the user is loaded. """ if not principal: return False principal = unwrap(principal) if not self.running: return True if isinstance(role, (Role, string_types)): role = (role,) # admin & manager always have role valid_roles = frozenset((Admin, Manager) + tuple(role)) if AnonymousRole in valid_roles: # everybody has the role 'Anonymous' return True if ( Authenticated in valid_roles and isinstance(principal, User) and not principal.is_anonymous ): return True if principal is AnonymousRole or ( hasattr(principal, "is_anonymous") and principal.is_anonymous ): # anonymous user, and anonymous role isn't in valid_roles return False # root always have any role if isinstance(principal, User) and principal.id == 0: return True if object: assert isinstance(object, Entity) object_key = "{}:{}".format(object.object_type, text_type(object.id)) if Creator in role: if object.creator == principal: return True if Owner in role: if object.owner == principal: return True else: object_key = None all_roles = ( self._fill_role_cache(principal) if self.app_state.use_cache else self._all_roles(principal) ) roles = set() roles |= all_roles.get(None, set()) roles |= all_roles.get(object_key, set()) return len(valid_roles & roles) > 0
def has_permission(self, user, permission, obj=None, inherit=False, roles=None): """ :param obj: target object to check permissions. :param inherit: check with permission inheritance. By default, check only local roles. :param roles: additional valid role or iterable of roles having `permission`. """ if not isinstance(permission, Permission): assert permission in PERMISSIONS permission = Permission(permission) user = unwrap(user) if not self.running: return True session = None if obj is not None: session = object_session(obj) if session is None: session = db.session() # root always have any permission if isinstance(user, User) and user.id == 0: return True # valid roles # 1: from database pa_filter = PermissionAssignment.object == None if obj is not None and obj.id is not None: pa_filter |= PermissionAssignment.object == obj pa_filter &= PermissionAssignment.permission == permission valid_roles = session.query(PermissionAssignment.role).filter(pa_filter) valid_roles = {res[0] for res in valid_roles.yield_per(1000)} # complete with defaults valid_roles |= {Admin} # always have all permissions valid_roles |= DEFAULT_PERMISSION_ROLE.get(permission, set()) # FIXME: obj.__class__ could define default permisssion matrix too if roles is not None: if isinstance(roles, (Role,) + string_types): roles = (roles,) for r in roles: valid_roles.add(Role(r)) # FIXME: query permission_role: global and on object if AnonymousRole in valid_roles: return True if Authenticated in valid_roles and not user.is_anonymous: return True # first test global roles, then object local roles checked_objs = [None, obj] if inherit and obj is not None: while obj.inherit_security and obj.parent is not None: obj = obj.parent checked_objs.append(obj) principals = [user] + list(user.groups) self._fill_role_cache_batch(principals) return any( ( self.has_role(principal, valid_roles, item) for principal in principals for item in checked_objs ) )
def query_entity_with_permission(self, permission, user=None, Model=Entity): """Filter a query on an :class:`Entity` or on of its subclasses. Usage:: read_q = security.query_entity_with_permission(READ, Model=MyModel) MyModel.query.filter(read_q) It should always be placed before any `.join()` happens in the query; else sqlalchemy might join to the "wrong" entity table when joining to other :class:`Entity`. :param user: user to filter for. Default: `current_user`. :param permission: required :class:`Permission` :param Model: An :class:`Entity` based class. Useful when there is more than one Entity based object in query, or if an alias should be used. :returns: a `sqlalchemy.sql.exists()` expression. """ assert isinstance(permission, Permission) assert issubclass(Model, Entity) RA = sa.orm.aliased(RoleAssignment) PA = sa.orm.aliased(PermissionAssignment) # id column from entity table. Model.id would refer to 'model' table. # this allows the DB to use indexes / foreign key constraints. id_column = sa.inspect(Model).primary_key[0] creator = Model.creator owner = Model.owner if not self.running: return sa.sql.exists([1]) if user is None: user = unwrap(current_user) # build role CTE principal_filter = RA.anonymous == True if not user.is_anonymous: principal_filter |= RA.user == user if user.groups: principal_filter |= RA.group_id.in_([g.id for g in user.groups]) RA = sa.sql.select([RA], principal_filter).cte() permission_exists = sa.sql.exists([1]).where( sa.sql.and_( PA.permission == permission, PA.object_id == id_column, (RA.c.role == PA.role) | (PA.role == AnonymousRole), (RA.c.object_id == PA.object_id) | (RA.c.object_id == None), ) ) # is_admin: self-explanatory. It search for local or global admin # role, but PermissionAssignment is not involved, thus it can match on # entities that don't have *any permission assignment*, whereas previous # expressions cannot. is_admin = sa.sql.exists([1]).where( sa.sql.and_( RA.c.role == Admin, (RA.c.object_id == id_column) | (RA.c.object_id == None), principal_filter, ) ) filter_expr = permission_exists | is_admin if user and not user.is_anonymous: is_owner_or_creator = sa.sql.exists([1]).where( sa.sql.and_( PA.permission == permission, PA.object_id == id_column, sa.sql.or_( (PA.role == Owner) & (owner == user), (PA.role == Creator) & (creator == user), ), ) ) filter_expr |= is_owner_or_creator return filter_expr