def test_edit_tag(users, http_client, base_url, session): user = session.query(User).filter_by(username="******").scalar() perm = Permission(name=TAG_EDIT, description="Why is this not nullable?") perm.add(session) session.commit() grant_permission(session.query(Group).filter_by(groupname="all-teams").scalar(), session.query(Permission).filter_by(name=TAG_EDIT).scalar(), "*") fe_url = url(base_url, '/tags') resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'tagname': "tyler_was_here", "description": "Test Tag Please Ignore"}), headers={'X-Grouper-User': user.username}) tag = PublicKeyTag.get(session, name="tyler_was_here") assert tag.description == "Test Tag Please Ignore", "The description should match what we created it with" user = session.query(User).filter_by(username="******").scalar() fe_url = url(base_url, '/tags/{}/edit'.format(tag.id)) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({"description": "Don't tag me bro"}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 tag = PublicKeyTag.get(session, name="tyler_was_here") assert tag.description == "Don't tag me bro", "The description should have been updated"
def create_permission(session: Session, name: str, description: Optional[str] = None) -> Permission: """Create and add a new permission to database.""" permission = Permission(name=name, description=description or "") permission.add(session) return permission
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 test_grant_and_revoke(session, standard_graph, graph, groups, permissions, http_client, base_url): """Test that permission grant and revokes are reflected correctly.""" group_name = "team-sre" permission_name = "sudo" user_name = "*****@*****.**" def _check_graph_for_perm(graph): return any(map(lambda x: x.permission == permission_name, graph.permission_metadata[group_name])) # make some permission admins perm_admin, _ = Permission.get_or_create(session, name=PERMISSION_ADMIN, description="") session.commit() grant_permission(groups["security-team"], perm_admin) # grant attempt by non-permission admin fe_url = url(base_url, "/permissions/grant/{}".format(group_name)) with pytest.raises(HTTPError): yield http_client.fetch(fe_url, method="POST", body=urlencode({"permission": permission_name, "argument": "specific_arg"}), headers={'X-Grouper-User': "******"}) graph.update_from_db(session) assert not _check_graph_for_perm(graph), "no permissions granted" # grant by permission admin resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({"permission": permission_name, "argument": "specific_arg"}), headers={'X-Grouper-User': user_name}) assert resp.code == 200 graph.update_from_db(session) assert _check_graph_for_perm(graph), "permissions granted, successfully" # figure out mapping_id of grant permission_id = Permission.get(session, name=permission_name).id group_id = Group.get(session, name=group_name).id mapping = session.query(PermissionMap).filter( PermissionMap.permission_id == permission_id, PermissionMap.group_id == group_id).first() # revoke permission by non-admin fe_url = url(base_url, "/permissions/{}/revoke/{}".format(permission_name, mapping.id)) with pytest.raises(HTTPError): yield http_client.fetch(fe_url, method="POST", body=urlencode({}), headers={'X-Grouper-User': "******"}) graph.update_from_db(session) assert _check_graph_for_perm(graph), "permissions not revoked" # revoke permission for realz resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({}), headers={'X-Grouper-User': user_name}) assert resp.code == 200 graph.update_from_db(session) assert not _check_graph_for_perm(graph), "permissions revoked successfully"
def test_revoke_permission_from_tag(users, http_client, base_url, session): user = session.query(User).filter_by(username="******").scalar() perm = Permission(name=TAG_EDIT, description="Why is this not nullable?") perm.add(session) session.commit() grant_permission( session.query(Group).filter_by(groupname="all-teams").scalar(), session.query(Permission).filter_by(name=TAG_EDIT).scalar(), "*") fe_url = url(base_url, '/tags') resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({ 'tagname': "tyler_was_here", "description": "Test Tag Please Ignore" }), headers={'X-Grouper-User': user.username}) tag = PublicKeyTag.get(session, name="tyler_was_here") user = session.query(User).filter_by(username="******").scalar() fe_url = url(base_url, '/permissions/grant_tag/{}'.format(tag.name)) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({ 'permission': TAG_EDIT, "argument": "*" }), headers={'X-Grouper-User': user.username}) assert resp.code == 200 tag = PublicKeyTag.get(session, name="tyler_was_here") perm = Permission.get(session, TAG_EDIT) assert len(get_public_key_tag_permissions( session, tag)) == 1, "The tag should have exactly 1 permission" user = session.query(User).filter_by(username="******").scalar() mapping = get_public_key_tag_permissions(session, tag)[0] fe_url = url( base_url, '/permissions/{}/revoke_tag/{}'.format(TAG_EDIT, mapping.mapping_id)) resp = yield http_client.fetch(fe_url, method="POST", body="", headers={'X-Grouper-User': user.username}) assert resp.code == 200 tag = PublicKeyTag.get(session, name="tyler_was_here") assert len(get_public_key_tag_permissions( session, tag)) == 0, "The tag should have no permissions"
def create_permission( self, name, description="", audited=False, enabled=True, created_on=None ): # type: (str, str, bool, bool, Optional[datetime]) -> None permission = SQLPermission( name=name, description=description, audited=audited, enabled=enabled ) if created_on: permission.created_on = created_on permission.add(self.session)
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 create_permission(session, name, description=""): # type: (Session, str, Optional[str]) -> Permission """Create and add a new permission to database Arg(s): session(models.base.session.Session): database session name(str): the name of the permission description(str): the description of the permission Returns: The created permission that has been added to the session """ permission = Permission(name=name, description=description or "") permission.add(session) return permission
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_limited_permissions_global_approvers(session, standard_graph, groups, grantable_permissions, http_client, base_url): """Test that notifications are not sent to global approvers.""" perm_grant, _, perm1, _ = grantable_permissions perm_admin, _ = Permission.get_or_create(session, name=PERMISSION_ADMIN, description="") session.commit() # one circuit-breaking admin grant, one wildcard grant grant_permission(groups["sad-team"], perm_admin, argument="") grant_permission(groups["security-team"], perm_grant, argument="grantable.*") security_team_members = {name for (t, name) in groups['security-team'].my_members().keys() if t == 'User'} # SPECIFIC REQUEST: 'grantable.one', 'specific_arg' for 'sad-team' groupname = "sad-team" username = "******" fe_url = url(base_url, "/groups/{}/permission/request".format(groupname)) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({"permission_name": perm1.name, "argument": "specific_arg", "reason": "blah blah black sheep", "argument_type": "text"}), headers={'X-Grouper-User': username}) assert resp.code == 200 emails = _get_unsent_and_mark_as_sent_emails(session) assert len(emails) == 2, "email only sent to security-team" assert not security_team_members.difference(e.email for e in emails), \ "only security-team members get notification"
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="grouper.admin.users permission") session.commit() grant_permission(groups["auditors"], user_admin_perm)
def revoke_all_service_account_grants(self, permission): # type: (str) -> List[ServiceAccountPermissionGrant] sql_permission = Permission.get(self.session, name=permission) if not sql_permission: return [] grants = ( self.session.query( ServiceAccountPermissionMap.id, User.username, ServiceAccountPermissionMap.argument, ServiceAccountPermissionMap.granted_on, ) .filter( User.id == ServiceAccount.user_id, ServiceAccount.id == ServiceAccountPermissionMap.service_account_id, PermissionMap.permission_id == sql_permission.id, ) .all() ) ids = [g.id for g in grants] self.session.query(ServiceAccountPermissionMap).filter( ServiceAccountPermissionMap.id.in_(ids) ).delete(synchronize_session="fetch") return [ ServiceAccountPermissionGrant( service_account=g.username, permission=permission, argument=g.argument, granted_on=g.granted_on, is_alias=False, grant_id=g.id, ) for g in grants ]
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="grouper.admin.users permission") session.commit() grant_permission(groups["auditors"], user_admin_perm)
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, name="ssh") assert "team-sre" in [g[0] for g in get_groups_by_permission(session, permission)] group.disable() assert "team-sre" not in [g[0] for g in get_groups_by_permission(session, permission)]
def service_account_grants_for_permission(self, name): # type: (str) -> List[ServiceAccountPermissionGrant] permission = Permission.get(self.session, name=name) if not permission or not permission.enabled: return [] grants = ( self.session.query( User.username, ServiceAccountPermissionMap.argument, ServiceAccountPermissionMap.granted_on, ServiceAccountPermissionMap.id, ) .filter( ServiceAccountPermissionMap.permission_id == permission.id, ServiceAccount.id == ServiceAccountPermissionMap.service_account_id, User.id == ServiceAccount.user_id, ) .order_by(User.username, ServiceAccountPermissionMap.argument) ) return [ ServiceAccountPermissionGrant( service_account=g.username, permission=name, argument=g.argument, granted_on=g.granted_on, is_alias=False, grant_id=g.id, ) for g in grants.all() ]
def user_grantable_permissions(session, user): ''' Returns a list of permissions this user is allowed to grant. Presently, this only counts permissions that a user has directly -- in other words, the 'grant' permissions are not counted as inheritable. TODO: consider making these permissions inherited? This requires walking the graph, which is expensive. Returns a list of tuples (Permission, argument) that the user is allowed to grant. ''' # avoid circular dependency from grouper.permissions import filter_grantable_permissions all_permissions = { permission.name: permission for permission in Permission.get_all(session) } if user_is_permission_admin(session, user): result = [(perm, '*') for perm in all_permissions.values()] return sorted(result, key=lambda x: x[0].name + x[1]) # Someone can grant a permission if they are a member of a group that has a permission # of PERMISSION_GRANT with an argument that matches the name of a permission. grants = [ x for x in user_permissions(session, user) if x.name == PERMISSION_GRANT ] return filter_grantable_permissions(session, grants)
def revoke_all_service_account_grants(self, permission): # type: (str) -> List[ServiceAccountPermissionGrant] sql_permission = Permission.get(self.session, name=permission) if not sql_permission: return [] grants = (self.session.query( ServiceAccountPermissionMap.id, User.username, ServiceAccountPermissionMap.argument, ServiceAccountPermissionMap.granted_on, ).filter( User.id == ServiceAccount.user_id, ServiceAccount.id == ServiceAccountPermissionMap.service_account_id, PermissionMap.permission_id == sql_permission.id, ).all()) ids = [g.id for g in grants] self.session.query(ServiceAccountPermissionMap).filter( ServiceAccountPermissionMap.id.in_(ids)).delete( synchronize_session="fetch") return [ ServiceAccountPermissionGrant( service_account=g.username, permission=permission, argument=g.argument, granted_on=g.granted_on, is_alias=False, grant_id=g.id, ) for g in grants ]
def service_account_grants_for_permission(self, name): # type: (str) -> List[ServiceAccountPermissionGrant] permission = Permission.get(self.session, name=name) if not permission or not permission.enabled: return [] grants = (self.session.query( User.username, ServiceAccountPermissionMap.argument, ServiceAccountPermissionMap.granted_on, ServiceAccountPermissionMap.id, ).filter( ServiceAccountPermissionMap.permission_id == permission.id, ServiceAccount.id == ServiceAccountPermissionMap.service_account_id, User.id == ServiceAccount.user_id, ).order_by(User.username, ServiceAccountPermissionMap.argument)) return [ ServiceAccountPermissionGrant( service_account=g.username, permission=name, argument=g.argument, granted_on=g.granted_on, is_alias=False, grant_id=g.id, ) for g in grants.all() ]
def group_grants_for_permission(self, name, include_disabled_groups=False, argument=None): # type: (str, bool, Optional[str]) -> List[GroupPermissionGrant] permission = Permission.get(self.session, name=name) if not permission or not permission.enabled: return [] grants = (self.session.query( Group.groupname, PermissionMap.argument, PermissionMap.id, PermissionMap.granted_on).filter( PermissionMap.permission_id == permission.id, Group.id == PermissionMap.group_id).order_by( Group.groupname, PermissionMap.argument)) if not include_disabled_groups: grants = grants.filter(Group.enabled == True) if argument: grants = grants.filter(PermissionMap.argument == argument) return [ GroupPermissionGrant( group=g.groupname, permission=name, argument=g.argument, granted_on=g.granted_on, is_alias=False, grant_id=g.id, ) for g in grants.all() ]
def revoke_all_group_grants(self, permission): # type: (str) -> List[GroupPermissionGrant] sql_permission = Permission.get(self.session, name=permission) if not sql_permission: return [] grants = ( self.session.query( PermissionMap.id, Group.groupname, PermissionMap.argument, PermissionMap.granted_on ) .filter( Group.id == PermissionMap.group_id, PermissionMap.permission_id == sql_permission.id, ) .all() ) ids = [g.id for g in grants] self.session.query(PermissionMap).filter(PermissionMap.id.in_(ids)).delete( synchronize_session="fetch" ) return [ GroupPermissionGrant( group=g.groupname, permission=permission, argument=g.argument, granted_on=g.granted_on, is_alias=False, grant_id=g.id, ) for g in grants ]
def group_grants_for_permission(self, name, include_disabled_groups=False): # type: (str, bool) -> List[GroupPermissionGrant] permission = Permission.get(self.session, name=name) if not permission or not permission.enabled: return [] grants = ( self.session.query( Group.groupname, PermissionMap.argument, PermissionMap.id, PermissionMap.granted_on ) .filter( PermissionMap.permission_id == permission.id, Group.id == PermissionMap.group_id ) .order_by(Group.groupname, PermissionMap.argument) ) if not include_disabled_groups: grants = grants.filter(Group.enabled == True) return [ GroupPermissionGrant( group=g.groupname, permission=name, argument=g.argument, granted_on=g.granted_on, is_alias=False, grant_id=g.id, ) for g in grants.all() ]
def sync_db_command(args): # Models not implicitly or explictly imported above are explicitly imported # here: from grouper.models.perf_profile import PerfProfile # noqa 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!" grant_permission(session, admin_group.id, permission.id) session.commit()
def get_owners_by_grantable_permission(session, separate_global=False): """ 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)) all_group_permissions = session.query( Permission.name, PermissionMap.argument, PermissionMap.granted_on, Group, ).filter( PermissionMap.group_id == Group.id, Permission.id == PermissionMap.permission_id, ).all() grants_by_group = defaultdict(list) for grant in all_group_permissions: grants_by_group[grant.Group.id].append(grant) for group in all_groups: # special case permission admins group_permissions = grants_by_group[group.id] 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) if separate_global: owners_by_arg_by_perm[GLOBAL_OWNERS]["*"].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 create_permission_requests(setup: SetupTest) -> None: """Create a permission requesting scenario. Set up a permission requesting scenario in which [email protected] has both inbound and outbound requests that they should be able to see on the requests page. """ with setup.transaction(): setup.create_permission("perm.hasgranter", description="perm with granter") setup.create_permission("perm.nogranter", description="perm without granter") setup.add_user_to_group("*****@*****.**", "auditors") setup.grant_permission_to_group(PERMISSION_GRANT, "perm.hasgranter/a", "auditors") setup.add_user_to_group("*****@*****.**", "group-admins") setup.grant_permission_to_group(PERMISSION_ADMIN, "", "group-admins") # The old API requires SQLAlchemy objects. granting_user = User.get(setup.session, name="*****@*****.**") assert granting_user granting_group = Group.get(setup.session, name="auditors") assert granting_group requesting_user = User.get(setup.session, name="*****@*****.**") assert requesting_user requesting_group = Group.get(setup.session, name="group-admins") assert requesting_group perm_granter = Permission.get(setup.session, "perm.hasgranter") assert perm_granter perm_nogranter = Permission.get(setup.session, "perm.nogranter") assert perm_nogranter perm_admin = Permission.get(setup.session, PERMISSION_ADMIN) assert perm_admin # The old APIs require a global settings object. set_global_settings(setup.settings) # Request the two test perms from group-admins. with setup.transaction(): create_request( setup.session, requesting_user, requesting_group, perm_granter, "a", "reasons" ) create_request( setup.session, requesting_user, requesting_group, perm_nogranter, "a", "reasons" ) # Finally make one more request from a user other than [email protected]. with setup.transaction(): create_request(setup.session, granting_user, granting_group, perm_admin, "a", "reasons")
def entries_affecting_permission(self, permission, limit): # type: (str, int) -> List[AuditLogEntry] permission_obj = Permission.get(self.session, name=permission) if not permission_obj: return [] results = (self.session.query(AuditLog).filter( AuditLog.on_permission_id == permission_obj.id).order_by( desc(AuditLog.log_time)).limit(limit)) return [self._to_audit_log_entry(e) for e in results]
def get_owners_by_grantable_permission(session, separate_global=False): """ 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)) all_group_permissions = session.query( Permission.name, PermissionMap.argument, PermissionMap.granted_on, Group, ).filter( PermissionMap.group_id == Group.id, Permission.id == PermissionMap.permission_id, ).all() grants_by_group = defaultdict(list) for grant in all_group_permissions: grants_by_group[grant.Group.id].append(grant) for group in all_groups: # special case permission admins group_permissions = grants_by_group[group.id] 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) if separate_global: owners_by_arg_by_perm[GLOBAL_OWNERS]["*"].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 res in get_plugin_proxy().get_owner_by_arg_by_perm(session): 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 permissions(session, users): permissions = { permission: Permission.get_or_create( session, name=permission, description="{} permission".format(permission))[0] for permission in ("ssh", "sudo", "audited", AUDIT_MANAGER, PERMISSION_AUDITOR) } enable_permission_auditing(session, permissions["audited"].name, users['*****@*****.**'].id) return permissions
def permissions(session, users): # type: (Session, Dict[str, User]) -> Dict[str, Permission] """Create a standard set of test permissions. Go to a bit of effort to use unique timestamps for the creation date of permissions, since it makes it easier to test sorting. Similarly, don't sort the list of permissions to create by name so that the date sort and the name sort are different. Do not use milliseconds in the creation timestamps, since the result will be different in SQLite (where they are preserved) and MySQL (where they are stripped). """ all_permissions = [ "owner", "ssh", "sudo", "audited", AUDIT_MANAGER, AUDIT_VIEWER, PERMISSION_AUDITOR, PERMISSION_ADMIN, "team-sre", USER_ADMIN, GROUP_ADMIN, ] created_on_seconds = int(time() - 1000) permissions = {} for name in all_permissions: permission = Permission.get(session, name=name) if not permission: created_on = datetime.utcfromtimestamp(created_on_seconds) created_on_seconds += 1 description = "{} permission".format(name) permission = Permission(name=name, description=description, created_on=created_on) permission.add(session) permissions[name] = permission enable_permission_auditing(session, permissions["audited"].name, users["*****@*****.**"].id) return permissions
def get_permission(self, name): # type: (str) -> Optional[Permission] permission = SQLPermission.get(self.session, name=name) if not permission: return None return Permission( name=permission.name, description=permission.description, created_on=permission.created_on, )
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 revoke_permission_from_group(self, permission, argument, group): # type: (str, str, str) -> None permission_obj = Permission.get(self.session, name=permission) assert permission_obj group_obj = Group.get(self.session, name=group) assert group_obj self.session.query(PermissionMap).filter( PermissionMap.permission_id == permission_obj.id, PermissionMap.group_id == group_obj.id, PermissionMap.argument == argument, ).delete()
def get_permission(session, name): # type: (Session, str) -> Optional[Permission] """Get a permission Arg(s): session(models.base.session.Session): database session name(str): the name of the permission Returns: The permission if found, None otherwise """ return Permission.get(session, name=name)
def entries_affecting_permission(self, permission, limit): # type: (str, int) -> List[AuditLogEntry] permission_obj = Permission.get(self.session, name=permission) if not permission_obj: return [] results = ( self.session.query(AuditLog) .filter(AuditLog.on_permission_id == permission_obj.id) .order_by(desc(AuditLog.log_time)) .limit(limit) ) return [self._to_audit_log_entry(e) for e in results]
def get_permission(self, name): # type: (str) -> Optional[Permission] permission = SQLPermission.get(self.session, name=name) if not permission: return None return Permission( name=permission.name, description=permission.description, created_on=permission.created_on, audited=permission.audited, enabled=permission.enabled, )
def test_permission_disable_denied(setup): # type: (SetupTest) -> None with setup.transaction(): setup.create_user("*****@*****.**") setup.create_permission("some-permission") mock_ui = MagicMock() usecase = setup.usecase_factory.create_disable_permission_usecase("*****@*****.**", mock_ui) usecase.disable_permission("some-permission") assert mock_ui.mock_calls == [ call.disable_permission_failed_permission_denied("some-permission") ] assert Permission.get(setup.session, name="some-permission").enabled
def grant_permission_to_group(self, permission, argument, group): # type: (str, str, str) -> None self.create_group(group) self.create_permission(permission) permission_obj = Permission.get(self.session, name=permission) assert permission_obj group_obj = Group.get(self.session, name=group) assert group_obj grant = PermissionMap(permission_id=permission_obj.id, group_id=group_obj.id, argument=argument) grant.add(self.session)
def test_edit_tag(users, http_client, base_url, session): user = session.query(User).filter_by(username="******").scalar() perm = Permission(name=TAG_EDIT, description="Why is this not nullable?") perm.add(session) session.commit() grant_permission( session.query(Group).filter_by(groupname="all-teams").scalar(), session.query(Permission).filter_by(name=TAG_EDIT).scalar(), "*") fe_url = url(base_url, '/tags') resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({ 'tagname': "tyler_was_here", "description": "Test Tag Please Ignore" }), headers={'X-Grouper-User': user.username}) tag = PublicKeyTag.get(session, name="tyler_was_here") assert tag.description == "Test Tag Please Ignore", "The description should match what we created it with" user = session.query(User).filter_by(username="******").scalar() fe_url = url(base_url, '/tags/{}/edit'.format(tag.id)) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode( {"description": "Don't tag me bro"}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 tag = PublicKeyTag.get(session, name="tyler_was_here") assert tag.description == "Don't tag me bro", "The description should have been updated"
def test_grant_permission_to_tag(users, http_client, base_url, session): user = session.query(User).filter_by(username="******").scalar() perm = Permission(name=TAG_EDIT, description="Why is this not nullable?") perm.add(session) session.commit() grant_permission(session.query(Group).filter_by(groupname="all-teams").scalar(), session.query(Permission).filter_by(name=TAG_EDIT).scalar(), "*") fe_url = url(base_url, '/tags') resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'tagname': "tyler_was_here", "description": "Test Tag Please Ignore"}), headers={'X-Grouper-User': user.username}) tag = PublicKeyTag.get(session, name="tyler_was_here") user = session.query(User).filter_by(username="******").scalar() fe_url = url(base_url, '/permissions/grant_tag/{}'.format(tag.name)) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'permission': TAG_EDIT, "argument": "*"}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 tag = PublicKeyTag.get(session, name="tyler_was_here") perm = Permission.get(session, TAG_EDIT) assert len(get_public_key_tag_permissions(session, tag)) == 1, "The tag should have exactly 1 permission" assert get_public_key_tag_permissions(session, tag)[0].name == perm.name, "The tag's permission should be the one we added" assert get_public_key_tag_permissions(session, tag)[0].argument == "*", "The tag's permission should be the one we added" # Make sure trying to add a permission to a tag doesn't fail horribly if it's already there user = session.query(User).filter_by(username="******").scalar() fe_url = url(base_url, '/permissions/grant_tag/{}'.format(tag.name)) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'permission': TAG_EDIT, "argument": "*"}), headers={'X-Grouper-User': user.username}) assert resp.code == 200
def test_revoke_permission_from_tag(users, http_client, base_url, session): user = session.query(User).filter_by(username="******").scalar() perm = Permission(name=TAG_EDIT, description="Why is this not nullable?") perm.add(session) session.commit() grant_permission(session.query(Group).filter_by(groupname="all-teams").scalar(), session.query(Permission).filter_by(name=TAG_EDIT).scalar(), "*") fe_url = url(base_url, '/tags') resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'tagname': "tyler_was_here", "description": "Test Tag Please Ignore"}), headers={'X-Grouper-User': user.username}) tag = PublicKeyTag.get(session, name="tyler_was_here") user = session.query(User).filter_by(username="******").scalar() fe_url = url(base_url, '/permissions/grant_tag/{}'.format(tag.name)) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'permission': TAG_EDIT, "argument": "*"}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 tag = PublicKeyTag.get(session, name="tyler_was_here") perm = Permission.get(session, TAG_EDIT) assert len(get_public_key_tag_permissions(session, tag)) == 1, "The tag should have exactly 1 permission" user = session.query(User).filter_by(username="******").scalar() mapping = get_public_key_tag_permissions(session, tag)[0] fe_url = url(base_url, '/permissions/{}/revoke_tag/{}'.format(TAG_EDIT, mapping.mapping_id)) resp = yield http_client.fetch(fe_url, method="POST", body="", headers={'X-Grouper-User': user.username}) assert resp.code == 200 tag = PublicKeyTag.get(session, name="tyler_was_here") assert len(get_public_key_tag_permissions(session, tag)) == 0, "The tag should have no permissions"
def test_tags(session, users, http_client, base_url, graph): user = session.query(User).filter_by(username="******").scalar() perm = Permission(name=TAG_EDIT, description="Why is this not nullable?") perm.add(session) session.commit() perm2 = Permission(name="it.literally.does.not.matter", description="Why is this not nullable?") perm2.add(session) session.commit() grant_permission(session.query(Group).filter_by(groupname="all-teams").scalar(), session.query(Permission).filter_by(name=TAG_EDIT).scalar(), "*") grant_permission(session.query(Group).filter_by(groupname="all-teams").scalar(), session.query(Permission).filter_by(name="it.literally.does.not.matter").scalar(), "*") tag = PublicKeyTag(name="tyler_was_here") tag.add(session) session.commit() tag = PublicKeyTag.get(session, name="tyler_was_here") user = session.query(User).filter_by(username="******").scalar() grant_permission_to_tag(session, tag.id, perm.id, "prod") user = session.query(User).filter_by(username="******").scalar() add_public_key(session, user, key1) key = session.query(PublicKey).filter_by(user_id=user.id).scalar() user = session.query(User).filter_by(username="******").scalar() add_tag_to_public_key(session, key, tag) user = session.query(User).filter_by(username="******").scalar() key = session.query(PublicKey).filter_by(user_id=user.id).scalar() assert len(get_public_key_permissions(session, key)) == 1, "The SSH Key should have only 1 permission" assert get_public_key_permissions(session, key)[0].name == TAG_EDIT, "The SSH key's permission should be TAG_EDIT" assert get_public_key_permissions(session, key)[0].argument == "prod", "The SSH key's permission argument should be restricted to the tag's argument" assert len(user_permissions(session, user)) > 1, "The user should have more than 1 permission" graph.update_from_db(session) fe_url = url(base_url, '/users/{}'.format(user.username)) resp = yield http_client.fetch(fe_url) assert resp.code == 200 body = json.loads(resp.body) pub_key = body['data']['user']['public_keys'][0] assert len(pub_key['tags']) == 1, "The public key should only have 1 tag" assert pub_key['tags'][0] == 'tyler_was_here', "The public key should have the tag we gave it"
def grant_permission_to_group(self, permission, argument, group): # type: (str, str, str) -> None sql_group = Group.get(self.session, name=group) if not sql_group: raise GroupNotFoundException(group) sql_permission = Permission.get(self.session, name=permission) if not sql_permission: raise PermissionNotFoundException(permission) mapping = PermissionMap( permission_id=sql_permission.id, group_id=sql_group.id, argument=argument ) mapping.add(self.session)
def test_permission_disable_denied(setup): # type: (SetupTest) -> None with setup.transaction(): setup.create_user("*****@*****.**") setup.create_permission("some-permission") mock_ui = MagicMock() usecase = setup.usecase_factory.create_disable_permission_usecase( "*****@*****.**", mock_ui) usecase.disable_permission("some-permission") assert mock_ui.mock_calls == [ call.disable_permission_failed_permission_denied("some-permission") ] assert Permission.get(setup.session, name="some-permission").enabled
def test_regress_permreq_global_approvers(session, standard_graph, groups, grantable_permissions, http_client, base_url): """Validates that we can render a permission request form where a global approver exists""" perm_grant, _, perm1, _ = grantable_permissions perm_admin, _ = Permission.get_or_create(session, name=PERMISSION_ADMIN, description="") session.commit() grant_permission(groups["security-team"], perm_admin) groupname = "sad-team" username = "******" fe_url = url(base_url, "/groups/{}/permission/request".format(groupname)) resp = yield http_client.fetch(fe_url, method="GET", headers={'X-Grouper-User': username}) assert resp.code == 200
def post(self, name=None): tag = PublicKeyTag.get(self.session, None, name) if not tag: return self.notfound() if not user_has_permission(self.session, self.current_user, TAG_EDIT, tag.name): return self.forbidden() form = PermissionGrantTagForm(self.request.arguments) form.permission.choices = [["", "(select one)"]] for perm in self.session.query(Permission).all(): form.permission.choices.append( [perm.name, "{} (*)".format(perm.name)]) if not form.validate(): return self.render("permission-grant-tag.html", form=form, tag=tag, alerts=self.get_form_alerts(form.errors)) permission = Permission.get(self.session, form.data["permission"]) if not permission: return self.notfound() # Shouldn't happen. success = grant_permission_to_tag(self.session, tag.id, permission.id, argument=form.data["argument"]) if not success: form.argument.errors.append( "Permission and Argument already mapped to this tag.") return self.render( "permission-grant-tag.html", form=form, tag=tag, alerts=self.get_form_alerts(form.errors), ) AuditLog.log(self.session, self.current_user.id, 'grant_permission_tag', 'Granted permission with argument: {}'.format( form.data["argument"]), on_permission_id=permission.id, on_tag_id=tag.id) return self.redirect("/tags/{}?refresh=yes".format(tag.name))
def grant_permission_to_service_account(self, permission, argument, service_account): # type: (str, str, str) -> None self.create_permission(permission) permission_obj = Permission.get(self.session, name=permission) assert permission_obj user_obj = User.get(self.session, name=service_account) assert user_obj, "Must create the service account first" assert user_obj.is_service_account grant = ServiceAccountPermissionMap( permission_id=permission_obj.id, service_account_id=user_obj.service_account.id, argument=argument, ) grant.add(self.session)
def permissions(session, users): permissions = { permission: Permission.get_or_create( session, name=permission, description="{} permission".format(permission))[0] for permission in ("ssh", "sudo", "audited", AUDIT_MANAGER, PERMISSION_AUDITOR, "team-sre", USER_ADMIN, GROUP_ADMIN) } enable_permission_auditing(session, permissions["audited"].name, users['*****@*****.**'].id) return permissions
def create_permission(self, name, description="", audited=False, enabled=True, created_on=None): # type: (str, str, bool, bool, Optional[datetime]) -> None """Create a permission, does nothing if it already exists. Avoid milliseconds in the creation timestamp since they behave differently in SQLite (which preserves them) and MySQL (which drops them). """ if Permission.get(self.session, name=name): return if not created_on: created_on = datetime.utcfromtimestamp(int(time())) permission = Permission( name=name, description=description, _audited=audited, enabled=enabled, created_on=created_on, ) permission.add(self.session)
def group_grants_for_permission(self, name, include_disabled_groups=False): # type: (str, bool) -> List[GroupPermissionGrant] permission = Permission.get(self.session, name=name) if not permission or not permission.enabled: return [] grants = ( self.session.query(Group.groupname, PermissionMap.argument) .filter( PermissionMap.permission_id == permission.id, Group.id == PermissionMap.group_id ) .order_by(Group.groupname, PermissionMap.argument) ) if not include_disabled_groups: grants = grants.filter(Group.enabled == True) return [GroupPermissionGrant(g.groupname, name, g.argument) for g in grants.all()]
def grant_permission_to_service_account(self, permission, argument, service): # type: (str, str, str) -> None sql_service = ServiceAccount.get(self.session, name=service) if not sql_service or not sql_service.user.enabled: raise ServiceAccountNotFoundException(service) sql_permission = Permission.get(self.session, name=permission) if not sql_permission: raise PermissionNotFoundException(permission) mapping = ServiceAccountPermissionMap( permission_id=sql_permission.id, service_account_id=sql_service.id, argument=argument) mapping.add(self.session)