def filter_grantable_permissions(session, grants, all_permissions=None): """For a given set of PERMISSION_GRANT permissions, return all permissions that are grantable. Args: session (sqlalchemy.orm.session.Session); database session grants ([Permission, ...]): PERMISSION_GRANT permissions all_permissions ({name: Permission}): all permissions to check against Returns: list of (Permission, argument) that is grantable by list of grants sorted by permission name and argument. """ if all_permissions is None: all_permissions = {permission.name: permission for permission in Permission.get_all(session)} result = [] for grant in grants: assert grant.name == PERMISSION_GRANT grantable = grant.argument.split('/', 1) if not grantable: continue for name, permission_obj in all_permissions.iteritems(): if matches_glob(grantable[0], name): result.append((permission_obj, grantable[1] if len(grantable) > 1 else '*', )) return sorted(result, key=lambda x: x[0].name + x[1])
def get_owner_arg_list(session, permission, argument, owners_by_arg_by_perm=None): """Return the grouper group(s) responsible for approving a request for the given permission + argument along with the actual argument they were granted. Args: session(sqlalchemy.orm.session.Session): database session permission(models.Permission): permission in question argument(str): argument for the permission owners_by_arg_by_perm(Dict): list of groups that can grant a given permission, argument pair in the format of {perm_name: {argument: [group1, group2, ...], ...}, ...} This is for convenience/caching if the value has already been fetched. Returns: list of 2-tuple of (group, argument) where group is the models.Group grouper groups responsibile for permimssion+argument and argument is the argument actually granted to that group. can be empty. """ if owners_by_arg_by_perm is None: owners_by_arg_by_perm = get_owners_by_grantable_permission(session) all_owner_arg_list = [] owners_by_arg = owners_by_arg_by_perm[permission.name] for arg, owners in owners_by_arg.items(): if matches_glob(arg, argument): all_owner_arg_list += [(owner, arg) for owner in owners] return all_owner_arg_list
def filter_grantable_permissions( session: Session, grants: List[Any], all_permissions: Optional[Dict[str, Permission]] = None ) -> List[Tuple[Permission, str]]: """For a set of PERMISSION_GRANT permissions, return all permissions that are grantable. Args: session: Database session grants: PERMISSION_GRANT permissions all_permissions: All permissions to check against (defaults to all permissions) Returns: List of (Permission, argument) that is grantable by list of grants, sorted by permission name and argument. """ if all_permissions is None: all_permissions = { permission.name: permission for permission in get_all_permissions(session) } result = [] for grant in grants: assert grant.name == PERMISSION_GRANT grantable = grant.argument.split("/", 1) if not grantable: continue for name, permission_obj in all_permissions.items(): if matches_glob(grantable[0], name): result.append((permission_obj, grantable[1] if len(grantable) > 1 else "*")) return sorted(result, key=lambda x: x[0].name + x[1])
def post(self, name=None, mapping_id=None): grantable = self.current_user.my_grantable_permissions() if not grantable: return self.forbidden() mapping = PermissionMap.get(self.session, id=mapping_id) if not mapping: return self.notfound() allowed = False for perm in grantable: if perm[0].name == mapping.permission.name: if matches_glob(perm[1], mapping.argument): allowed = True if not allowed: return self.forbidden() permission = mapping.permission group = mapping.group mapping.delete(self.session) self.session.commit() AuditLog.log(self.session, self.current_user.id, 'revoke_permission', 'Revoked permission with argument: {}'.format(mapping.argument), on_group_id=group.id, on_permission_id=permission.id) return self.redirect('/groups/{}?refresh=yes'.format(group.name))
def get_owner_arg_list( session: Session, permission: Permission, argument: str, owners_by_arg_by_perm: Optional[Dict[object, Dict[str, List[Group]]]] = None, ) -> List[Tuple[Group, str]]: """Determine the Grouper groups responsible for approving a request. Return the grouper groups responsible for approving a request for the given permission + argument along with the actual argument they were granted. Args: session: Database session permission: Permission in question argument: Argument for the permission owners_by_arg_by_perm: Groups that can grant a given permission, argument pair in the format of {perm_name: {argument: [group1, group2, ...], ...}, ...} This is for convenience/caching if the value has already been fetched. Returns: List of 2-tuple of (group, argument) where group is the Group for the Grouper groups responsibile for permimssion + argument, and argument is the argument actually granted to that group. Can be empty. """ if owners_by_arg_by_perm is None: owners_by_arg_by_perm = get_owners_by_grantable_permission(session) all_owner_arg_list: List[Tuple[Group, str]] = [] owners_by_arg = owners_by_arg_by_perm[permission.name] for arg, owners in owners_by_arg.items(): if matches_glob(arg, argument): all_owner_arg_list += [(owner, arg) for owner in owners] return all_owner_arg_list
def get_owner_arg_list(session, permission, argument, owners_by_arg_by_perm=None): """Return the grouper group(s) responsible for approving a request for the given permission + argument along with the actual argument they were granted. Args: session(sqlalchemy.orm.session.Session): database session permission(models.Permission): permission in question argument(str): argument for the permission Returns: list of 2-tuple of (group, argument) where group is the models.Group grouper groups responsibile for permimssion+argument and argument is the argument actually granted to that group. can be empty. """ if owners_by_arg_by_perm is None: owners_by_arg_by_perm = get_owners_by_grantable_permission(session) all_owner_arg_list = [] owners_by_arg = owners_by_arg_by_perm[permission.name] for arg, owners in owners_by_arg.items(): if matches_glob(arg, argument): all_owner_arg_list += [(owner, arg) for owner in owners] return all_owner_arg_list
def permissions_grantable_by_service_account(self, service): # type: (str) -> List[Tuple[str, str]] """Returns a name-sorted list of (permission, argument glob) pairs a service can grant.""" pagination = Pagination(sort_key=ListPermissionsSortKey.NAME, reverse_sort=False, offset=0, limit=None) all_permissions = self.permission_repository.list_permissions( pagination, False).values if self.service_account_is_permission_admin(service): return [(p.name, "*") for p in all_permissions] grants = self.permission_grant_repository.permission_grants_for_service_account( service) grants_of_permission_grant = [ g for g in grants if g.permission == PERMISSION_GRANT ] result = [] for grant in grants_of_permission_grant: grantable = grant.argument.split("/", 1) if not grantable: continue for permission in all_permissions: if matches_glob(grantable[0], permission.name): result.append( (permission.name, grantable[1] if len(grantable) > 1 else "*")) return result
def get_owner_arg_list(session, permission, argument, owners_by_arg_by_perm=None): """Return the grouper group(s) responsible for approving a request for the given permission + argument along with the actual argument they were granted. Args: session(sqlalchemy.orm.session.Session): database session permission(models.Permission): permission in question argument(str): argument for the permission owners_by_arg_by_perm(Dict): list of groups that can grant a given permission, argument pair in the format of {perm_name: {argument: [group1, group2, ...], ...}, ...} This is for convenience/caching if the value has already been fetched. Returns: list of 2-tuple of (group, argument) where group is the models.Group grouper groups responsibile for permimssion+argument and argument is the argument actually granted to that group. can be empty. """ if owners_by_arg_by_perm is None: owners_by_arg_by_perm = get_owners_by_grantable_permission(session) all_owner_arg_list = [] owners_by_arg = owners_by_arg_by_perm[permission.name] for arg, owners in iteritems(owners_by_arg): if matches_glob(arg, argument): all_owner_arg_list += [(owner, arg) for owner in owners] return all_owner_arg_list
def post(self): can_create = user_creatable_permissions(self.session, self.current_user) if not can_create: return self.forbidden() form = PermissionCreateForm(self.request.arguments) if not form.validate(): return self.render("permission-create.html", form=form, alerts=self.get_form_alerts(form.errors)) # A user is allowed to create a permission if the name matches any of the globs that they # are given access to via PERMISSION_CREATE, as long as the permission does not match a # reserved name. (Unless specifically granted.) allowed = False for creatable in can_create: if matches_glob(creatable, form.data["name"]): allowed = True for failure_message in test_reserved_names(form.data["name"]): form.name.errors.append(failure_message) if not allowed: form.name.errors.append( "Permission name does not match any of your allowed patterns.") if form.name.errors: return self.render("permission-create.html", form=form, alerts=self.get_form_alerts(form.errors)) try: permission = create_permission(self.session, form.data["name"], form.data["description"]) self.session.flush() except IntegrityError: self.session.rollback() form.name.errors.append( "Name already in use. Permissions must be unique.") return self.render( "permission-create.html", form=form, can_create=can_create, alerts=self.get_form_alerts(form.errors), ) self.session.commit() AuditLog.log( self.session, self.current_user.id, "create_permission", "Created permission.", on_permission_id=permission.id, ) # No explicit refresh because handler queries SQL. return self.redirect("/permissions/{}".format(permission.name))
def post(self): can_create = user_creatable_permissions(self.session, self.current_user) if not can_create: return self.forbidden() form = PermissionCreateForm(self.request.arguments) if not form.validate(): return self.render( "permission-create.html", form=form, alerts=self.get_form_alerts(form.errors) ) # A user is allowed to create a permission if the name matches any of the globs that they # are given access to via PERMISSION_CREATE, as long as the permission does not match a # reserved name. (Unless specifically granted.) allowed = False for creatable in can_create: if matches_glob(creatable, form.data["name"]): allowed = True for failure_message in test_reserved_names(form.data["name"]): form.name.errors.append(failure_message) if not allowed: form.name.errors.append("Permission name does not match any of your allowed patterns.") if form.name.errors: return self.render( "permission-create.html", form=form, alerts=self.get_form_alerts(form.errors) ) try: permission = create_permission( self.session, form.data["name"], form.data["description"] ) self.session.flush() except IntegrityError: self.session.rollback() form.name.errors.append("Name already in use. Permissions must be unique.") return self.render( "permission-create.html", form=form, can_create=sorted(can_create), alerts=self.get_form_alerts(form.errors), ) self.session.commit() AuditLog.log( self.session, self.current_user.id, "create_permission", "Created permission.", on_permission_id=permission.id, ) # No explicit refresh because handler queries SQL. return self.redirect("/permissions/{}".format(permission.name))
def group_has_matching_permission_grant(self, group, permission, argument): # type: (str, str, str) -> bool grants = self.permission_grant_repository.permission_grants_for_group( group) for grant in grants: if grant.permission == permission: if matches_glob(grant.argument, argument): return True return False
def post(self, group_id=None, name=None, account_id=None, accountname=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() service_account = ServiceAccount.get(self.session, account_id, accountname) if not service_account: return self.notfound() user = service_account.user if not self.check_access(self.session, self.current_user, service_account): return self.forbidden() grantable = group.my_permissions() form = self.get_form(grantable) if not form.validate(): return self.render( "service-account-permission-grant.html", form=form, user=user, group=group, alerts=self.get_form_alerts(form.errors) ) permission = Permission.get(self.session, form.data["permission"]) if not permission: return self.notfound() allowed = False for perm in grantable: if perm[1] == permission.name: if matches_glob(perm[3], form.data["argument"]): allowed = True break if not allowed: form.argument.errors.append( "The group {} does not have that permission".format(group.name)) return self.render( "service-account-permission-grant.html", form=form, user=user, group=group, alerts=self.get_form_alerts(form.errors) ) try: grant_permission_to_service_account( self.session, service_account, permission, form.data["argument"]) except IntegrityError: self.session.rollback() return self.render( "service-account-permission-grant.html", form=form, user=user, alerts=self.get_form_alerts(form.errors) ) AuditLog.log(self.session, self.current_user.id, "grant_permission", "Granted permission with argument: {}".format(form.data["argument"]), on_permission_id=permission.id, on_group_id=group.id, on_user_id=service_account.user.id) return self.redirect("/groups/{}/service/{}?refresh=yes".format( group.name, service_account.user.username))
def check_access(session: Session, mapping: PermissionMap, user: User): user_is_owner = user_is_owner_of_group(session, mapping.group, user) if user_is_owner: return True grantable = user_grantable_permissions(session, user) for perm in grantable: if perm[0].name == mapping.permission.name: if matches_glob(perm[1], mapping.argument): return True return False
def check_access(session, mapping, user): user_is_owner = user_is_owner_of_group(session, mapping.group, user) if user_is_owner: return True grantable = user_grantable_permissions(session, user) for perm in grantable: if perm[0].name == mapping.permission.name: if matches_glob(perm[1], mapping.argument): return True return False
def grant_permission_to_service_account(self, permission, argument, service): # type: (str, str, str) -> None if not self.service_account_service.service_account_is_enabled( service): self.ui.grant_permission_to_service_account_failed_service_account_not_found( service) return valid, error = self.permission_service.is_valid_permission_argument( permission, argument) if not valid: assert error self.ui.grant_permission_to_service_account_failed_invalid_argument( permission, argument, service, error) return allowed = False grantable = self.permissions_grantable_to_service_account(service) for grantable_perm, grantable_arg in grantable: if grantable_perm == permission and matches_glob( grantable_arg, argument): allowed = True break if not allowed: message = ( "Permission denied. To grant a permission to a service account you must either " "independently have the ability to grant that permission, or the owner group " "must have that permission and you must be a member of that owning group." ) self.ui.grant_permission_to_service_account_failed_permission_denied( permission, argument, service, message) return authorization = Authorization(self.actor) with self.transaction_service.transaction(): try: self.service_account_service.grant_permission_to_service_account( permission, argument, service, authorization) except PermissionNotFoundException: # It should be impossible to hit this exception. In order to get this far, the # perm must be on the list of perms the actor can grant, and thus must exist. # Leaving the logic here however in case that changes in the future. self.ui.grant_permission_to_service_account_failed_permission_not_found( permission, service) return self.ui.granted_permission_to_service_account(permission, argument, service)
def get(self, name=None, mapping_id=None): grantable = self.current_user.my_grantable_permissions() if not grantable: return self.forbidden() mapping = PermissionMap.get(self.session, id=mapping_id) if not mapping: return self.notfound() allowed = False for perm in grantable: if perm[0].name == mapping.permission.name: if matches_glob(perm[1], mapping.argument): allowed = True if not allowed: return self.forbidden() self.render("permission-revoke.html", mapping=mapping)
def permissions_grantable_by_user(self, user): # type: (str) -> List[Tuple[str, str]] """Returns a name-sorted list of all (permission, argument glob) pairs a user can grant. NOTE: The list of grantable permissions is calculated based on _all_ grants of the PERMISSION_GRANT permission that the user has. In particular this includes indirectly inherited grants. As of writing, this behavior differs from the legacy non-hexagonal logic, so anything relying on that old logic will act differently. """ pagination = Pagination(sort_key=ListPermissionsSortKey.NAME, reverse_sort=False, offset=0, limit=None) all_permissions = self.permission_repository.list_permissions( pagination, False).values if self.user_is_permission_admin(user): return [(p.name, "*") for p in all_permissions] all_grants = self.permission_grant_repository.permission_grants_for_user( user) grants_of_permission_grant = [ g for g in all_grants if g.permission == PERMISSION_GRANT ] result = [] for grant in grants_of_permission_grant: grantable = grant.argument.split("/", 1) if not grantable: continue for permission in all_permissions: if matches_glob(grantable[0], permission.name): result.append( (permission.name, grantable[1] if len(grantable) > 1 else "*")) return result
def post(self, name=None): grantable = self.current_user.my_grantable_permissions() if not grantable: return self.forbidden() group = Group.get(self.session, None, name) if not group: return self.notfound() form = PermissionGrantForm(self.request.arguments) form.permission.choices = [["", "(select one)"]] for perm in grantable: grantable_str = "{} ({})".format(perm[0].name, perm[1]) form.permission.choices.append([perm[0].name, grantable_str]) if not form.validate(): return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors) ) permission = Permission.get(self.session, form.data["permission"]) if not permission: return self.notfound() # Shouldn't happen. allowed = False for perm in grantable: if perm[0].name == permission.name: if matches_glob(perm[1], form.data["argument"]): allowed = True if not allowed: form.argument.errors.append("You do not have grant authority over that permission/argument combination.") return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors) ) # If the permission is audited, then see if the subtree meets auditing requirements. if permission.audited: fail_message = ( "Permission is audited and this group (or a subgroup) contains " + "owners, np-owners, or managers who have not received audit training." ) try: permission_ok = assert_controllers_are_auditors(group) except UserNotAuditor as e: permission_ok = False fail_message = e if not permission_ok: form.permission.errors.append(fail_message) return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors) ) try: group.grant_permission(permission, argument=form.data["argument"]) except IntegrityError: form.argument.errors.append("Permission and Argument already mapped to this group.") return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors) ) self.session.commit() AuditLog.log( self.session, self.current_user.id, "grant_permission", "Granted permission with argument: {}".format(form.data["argument"]), on_permission_id=permission.id, on_group_id=group.id, ) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def post(self, group_id=None, name=None, account_id=None, accountname=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() service_account = ServiceAccount.get(self.session, account_id, accountname) if not service_account: return self.notfound() user = service_account.user if not self.check_access(self.session, self.current_user, service_account): return self.forbidden() grantable = group.my_permissions() form = self.get_form(grantable) if not form.validate(): return self.render("service-account-permission-grant.html", form=form, user=user, group=group, alerts=self.get_form_alerts(form.errors)) permission = Permission.get(self.session, form.data["permission"]) if not permission: return self.notfound() allowed = False for perm in grantable: if perm[1] == permission.name: if matches_glob(perm[3], form.data["argument"]): allowed = True break if not allowed: form.argument.errors.append( "The group {} does not have that permission".format( group.name)) return self.render("service-account-permission-grant.html", form=form, user=user, group=group, alerts=self.get_form_alerts(form.errors)) try: grant_permission_to_service_account(self.session, service_account, permission, form.data["argument"]) except IntegrityError: self.session.rollback() return self.render("service-account-permission-grant.html", form=form, user=user, alerts=self.get_form_alerts(form.errors)) AuditLog.log(self.session, self.current_user.id, "grant_permission", "Granted permission with argument: {}".format( form.data["argument"]), on_permission_id=permission.id, on_group_id=group.id, on_user_id=service_account.user.id) return self.redirect("/groups/{}/service/{}?refresh=yes".format( group.name, service_account.user.username))
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") grantable = user_grantable_permissions(self.session, self.current_user) if not grantable: return self.forbidden() group = Group.get(self.session, name=name) if not group: return self.notfound() form = PermissionGrantForm(self.request.arguments) form.permission.choices = [["", "(select one)"]] for perm in grantable: grantable_str = "{} ({})".format(perm[0].name, perm[1]) form.permission.choices.append([perm[0].name, grantable_str]) if not form.validate(): return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) permission = get_permission(self.session, form.data["permission"]) if not permission: return self.notfound() # Shouldn't happen. argument = form.argument.data.strip() allowed = False for perm in grantable: if perm[0].name == permission.name: if matches_glob(perm[1], argument): allowed = True break if not allowed: form.argument.errors.append( "You do not have grant authority over that permission/argument combination." ) return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) # If the permission is audited, then see if the subtree meets auditing requirements. if permission.audited: try: assert_controllers_are_auditors(group) except UserNotAuditor as e: form.permission.errors.append(str(e)) return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) try: self.plugins.check_permission_argument(permission.name, argument) grant_permission(self.session, group.id, permission.id, argument=argument) except PluginRejectedPermissionArgument as e: self.session.rollback() form.argument.errors.append(f"Rejected by plugin: {e}") return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) except IntegrityError: self.session.rollback() form.argument.errors.append( "Permission and Argument already mapped to this group.") return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) self.session.commit() AuditLog.log( self.session, self.current_user.id, "grant_permission", "Granted permission with argument: {}".format( form.data["argument"]), on_permission_id=permission.id, on_group_id=group.id, ) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def post(self, name=None): grantable = user_grantable_permissions(self.session, self.current_user) if not grantable: return self.forbidden() group = Group.get(self.session, None, name) if not group: return self.notfound() form = PermissionGrantForm(self.request.arguments) form.permission.choices = [["", "(select one)"]] for perm in grantable: grantable_str = "{} ({})".format(perm[0].name, perm[1]) form.permission.choices.append([perm[0].name, grantable_str]) if not form.validate(): return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) permission = get_permission(self.session, form.data["permission"]) if not permission: return self.notfound() # Shouldn't happen. allowed = False for perm in grantable: if perm[0].name == permission.name: if matches_glob(perm[1], form.data["argument"]): allowed = True break if not allowed: form.argument.errors.append( "You do not have grant authority over that permission/argument combination." ) return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) # If the permission is audited, then see if the subtree meets auditing requirements. if permission.audited: fail_message = ( "Permission is audited and this group (or a subgroup) contains " + "owners, np-owners, or managers who have not received audit training." ) try: permission_ok = assert_controllers_are_auditors(group) except UserNotAuditor as e: permission_ok = False fail_message = e if not permission_ok: form.permission.errors.append(fail_message) return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) try: grant_permission(self.session, group.id, permission.id, argument=form.data["argument"]) except IntegrityError: self.session.rollback() form.argument.errors.append( "Permission and Argument already mapped to this group.") return self.render( "permission-grant.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) self.session.commit() AuditLog.log( self.session, self.current_user.id, "grant_permission", "Granted permission with argument: {}".format( form.data["argument"]), on_permission_id=permission.id, on_group_id=group.id, ) return self.redirect("/groups/{}?refresh=yes".format(group.name))