def grantable_permissions(session, standard_graph): perm_grant, _ = Permission.get_or_create(session, name=PERMISSION_GRANT, description="") perm0, _ = Permission.get_or_create(session, name="grantable", description="") perm1, _ = Permission.get_or_create(session, name="grantable.one", description="") perm2, _ = Permission.get_or_create(session, name="grantable.two", description="") session.commit() return perm_grant, perm0, perm1, perm2
def post(self): can_create = self.current_user.my_creatable_permissions() 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), ) permission = Permission(name=form.data["name"], description=form.data["description"]) try: permission.add(self.session) 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 test_permission_grant_to_owners(session, standard_graph, groups, grantable_permissions): """Test we're getting correct owners according to granted 'grouper.permission.grant' permissions.""" perm_grant, _, perm1, perm2 = grantable_permissions assert not get_owners_by_grantable_permission(session), 'nothing to begin with' # grant a grant on a non-existent permission grant_permission(groups["auditors"], perm_grant, argument="notgrantable.one") assert not get_owners_by_grantable_permission(session), 'ignore grants for non-existent perms' # grant a wildcard grant -- make sure all permissions are represented and # the grant isn't inherited grant_permission(groups["all-teams"], perm_grant, argument="grantable.*") owners_by_arg_by_perm = get_owners_by_grantable_permission(session) expected = [groups['all-teams']] assert owners_by_arg_by_perm[perm1.name]['*'] == expected, 'grants are not inherited' assert len(owners_by_arg_by_perm) == 2 assert len(owners_by_arg_by_perm[perm1.name]) == 1 assert len(owners_by_arg_by_perm[perm2.name]) == 1 # grant on argument substring grant_permission(groups["team-sre"], perm_grant, argument="{}/somesubstring*".format( perm1.name)) owners_by_arg_by_perm = get_owners_by_grantable_permission(session) expected = [groups['all-teams']] assert owners_by_arg_by_perm[perm1.name]['*'] == expected expected = [groups["team-sre"]] assert owners_by_arg_by_perm[perm1.name]['somesubstring*'] == expected # make sure get_owner() respect substrings res = [o for o, a in get_owner_arg_list(session, perm1, "somesubstring", owners_by_arg_by_perm=owners_by_arg_by_perm)] assert (sorted(res) == sorted([groups["all-teams"], groups["team-sre"]]), "should include substring wildcard matches") res = [o for o, a in get_owner_arg_list(session, perm1, "othersubstring", owners_by_arg_by_perm=owners_by_arg_by_perm)] assert sorted(res) == [groups["all-teams"]], "negative test of substring wildcard matches" # permission admins have all the power perm_admin, _ = Permission.get_or_create(session, name=PERMISSION_ADMIN, description="") session.commit() grant_permission(groups["security-team"], perm_admin) owners_by_arg_by_perm = get_owners_by_grantable_permission(session) all_permissions = Permission.get_all(session) for perm in all_permissions: assert perm.name in owners_by_arg_by_perm, 'all permission should be represented' assert groups["security-team"] in owners_by_arg_by_perm[perm.name]["*"], \ 'permission admin should be wildcard owners'
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 test_permission_exclude_inactive(session, standard_graph): """Ensure disabled groups are excluded from permission data.""" group = Group.get(session, name="team-sre") permission = Permission.get(session, "ssh") assert "team-sre" in [g[0] for g in permission.get_mapped_groups()] group.disable() assert "team-sre" not in [g[0] for g in permission.get_mapped_groups()]
def permissions(session): permissions = { permission: Permission.get_or_create( session, name=permission, description="{} permission".format(permission))[0] for permission in ("ssh", "sudo") } session.commit() return permissions
def permissions(session): permissions = { permission: Permission.get_or_create( session, name=permission, description="{} permission".format(permission))[0] for permission in ("ssh", "sudo", "audited", PERMISSION_AUDITOR) } permissions["audited"].enable_auditing() session.commit() return permissions
def sync_db_command(args): db_engine = get_db_engine(get_database_url(settings)) Model.metadata.create_all(db_engine) # Add some basic database structures we know we will need if they don't exist. session = make_session() for name, description in SYSTEM_PERMISSIONS: test = Permission.get(session, name) if test: continue permission = Permission(name=name, description=description) try: permission.add(session) session.flush() except IntegrityError: session.rollback() raise Exception('Failed to create permission: %s' % (name, )) session.commit() # This group is needed to bootstrap a Grouper installation. admin_group = Group.get(session, name="grouper-administrators") if not admin_group: admin_group = Group( groupname="grouper-administrators", description="Administrators of the Grouper system.", canjoin="nobody", ) try: admin_group.add(session) session.flush() except IntegrityError: session.rollback() raise Exception('Failed to create group: grouper-administrators') for permission_name in (GROUP_ADMIN, PERMISSION_ADMIN, USER_ADMIN): permission = Permission.get(session, permission_name) assert permission, "Permission should have been created earlier!" admin_group.grant_permission(permission) session.commit()
def get_owners_by_grantable_permission(session): """ Returns all known permission arguments with owners. This consolidates permission grants supported by grouper itself as well as any grants governed by plugins. Args: session(sqlalchemy.orm.session.Session): database session Returns: A map of permission to argument to owners of the form {permission: {argument: [owner1, ...], }, } where 'owners' are models.Group objects. And 'argument' can be '*' which means 'anything'. """ all_permissions = {permission.name: permission for permission in Permission.get_all(session)} all_groups = session.query(Group).filter(Group.enabled == True).all() owners_by_arg_by_perm = defaultdict(lambda: defaultdict(list)) for group in all_groups: group_permissions = session.query( Permission.name, PermissionMap.argument, PermissionMap.granted_on, Group, ).filter( PermissionMap.group_id == Group.id, Group.id == group.id, Permission.id == PermissionMap.permission_id, ).all() # special case permission admins if any(filter(lambda g: g.name == PERMISSION_ADMIN, group_permissions)): for perm_name in all_permissions: owners_by_arg_by_perm[perm_name]["*"].append(group) continue grants = [gp for gp in group_permissions if gp.name == PERMISSION_GRANT] for perm, arg in filter_grantable_permissions(session, grants, all_permissions=all_permissions): owners_by_arg_by_perm[perm.name][arg].append(group) # merge in plugin results for plugin in get_plugins(): res = plugin.get_owner_by_arg_by_perm(session) or {} for perm, owners_by_arg in res.items(): for arg, owners in owners_by_arg.items(): owners_by_arg_by_perm[perm][arg] += owners return owners_by_arg_by_perm
def get(self, name=None): # TODO: use cached data instead, add refresh to appropriate redirects. permission = Permission.get(self.session, name) if not permission: return self.notfound() can_delete = self.current_user.permission_admin mapped_groups = permission.get_mapped_groups() log_entries = permission.my_log_entries() self.render( "permission.html", permission=permission, can_delete=can_delete, mapped_groups=mapped_groups, log_entries=log_entries, )
def post(self, name=None): if not self.current_user.permission_admin: return self.forbidden() permission = Permission.get(self.session, name) if not permission: return self.notfound() permission.enable_auditing() self.session.commit() AuditLog.log(self.session, self.current_user.id, 'enable_auditing', 'Enabled auditing.', on_permission_id=permission.id) # No explicit refresh because handler queries SQL. return self.redirect("/permissions/{}".format(permission.name))
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): group = Group.get(self.session, group_id, name) if not group: return self.notfound() # only owner of group can request permissions for that group role_index = self.current_user.my_role_index(group.my_members()) if role_index not in OWNER_ROLE_INDICES: return self.forbidden() # check inputs args_by_perm = get_grantable_permissions(self.session, settings.restricted_ownership_permissions) dropdown_form, text_form = GroupPermissionRequest._get_forms(args_by_perm, self.request.arguments) argument_type = self.request.arguments.get("argument_type") if argument_type and argument_type[0] == "text": form = text_form elif argument_type and argument_type[0] == "dropdown": form = dropdown_form form.argument.choices = [(a, a) for a in args_by_perm[form.permission_name.data]] else: # someone messing with the form self.log_message("unknown argument type", group_name=group.name, argument_type=argument_type) return self.forbidden() if not form.validate(): return self.render( "group-permission-request.html", dropdown_form=dropdown_form, text_form=text_form, group=group, args_by_perm_json=json.dumps(args_by_perm), alerts=self.get_form_alerts(form.errors), dropdown_help=settings.permission_request_dropdown_help, text_help=settings.permission_request_text_help, ) permission = Permission.get(self.session, form.permission_name.data) assert permission is not None, "our prefilled permission should exist or we have problems" # save off request try: permissions.create_request(self.session, self.current_user, group, permission, form.argument.data, form.reason.data) except permissions.RequestAlreadyGranted: alerts = [Alert("danger", "This group already has this permission and argument.")] except permissions.RequestAlreadyExists: alerts = [Alert("danger", "Request for permission and argument already exists, please wait patiently.")] except permissions.NoOwnersAvailable: self.log_message("prefilled perm+arg have no owner", group_name=group.name, permission_name=permission.name, argument=form.argument.data) alerts = [Alert("danger", "No owners available for requested permission and argument." " If this error persists please contact an adminstrator.")] else: alerts = None if alerts: return self.render( "group-permission-request.html", dropdown_form=dropdown_form, text_form=text_form, group=group, args_by_perm_json=json.dumps(args_by_perm), alerts=alerts, ) else: return self.redirect("/groups/{}".format(group.name))
def user_admin_perm_to_auditors(session, groups): """Adds a USER_ADMIN permission to the "auditors" group""" user_admin_perm, is_new = Permission.get_or_create(session, name=USER_ADMIN, description="") session.commit() grant_permission(groups["auditors"], user_admin_perm)