def test_sa_pubkeys(session, users, http_client, base_url): user = users['*****@*****.**'] # Add account create_role_user(session, user, '*****@*****.**', 'Hi', 'canjoin') u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_role_user(session, user=u) assert is_role_user(session, group=g) assert get_role_user(session, user=u).group.id == g.id assert get_role_user(session, group=g).user.id == u.id assert not is_role_user(session, user=user) assert not is_role_user(session, group=Group.get(session, name="team-sre")) assert not get_public_keys_of_user(session, user.id) with pytest.raises(HTTPError): # add it fe_url = url(base_url, '/users/{}/public-key/add'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'public_key': SSH_KEY_1}), headers={'X-Grouper-User': "******"}) # add it fe_url = url(base_url, '/users/{}/public-key/add'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'public_key': SSH_KEY_1}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 # add bad key -- shouldn't add fe_url = url(base_url, '/users/{}/public-key/add'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'public_key': SSH_KEY_BAD}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 sa = User.get(session, name="*****@*****.**") keys = get_public_keys_of_user(session, sa.id) assert len(keys) == 1 assert keys[0].public_key == SSH_KEY_1 with pytest.raises(HTTPError): # delete it fe_url = url(base_url, '/users/{}/public-key/{}/delete'.format("*****@*****.**", keys[0].id)) resp = yield http_client.fetch(fe_url, method="POST", body='', headers={'X-Grouper-User': "******"}) # delete it fe_url = url(base_url, '/users/{}/public-key/{}/delete'.format("*****@*****.**", keys[0].id)) resp = yield http_client.fetch(fe_url, method="POST", body='', headers={'X-Grouper-User': user.username}) assert resp.code == 200 sa = User.get(session, name="*****@*****.**") assert not get_public_keys_of_user(session, sa.id)
def test_sa_tokens(session, users, http_client, base_url): user = users['*****@*****.**'] # Add account create_role_user(session, user, '*****@*****.**', 'Hi', 'canjoin') u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_role_user(session, user=u) assert is_role_user(session, group=g) assert get_role_user(session, user=u).group.id == g.id assert get_role_user(session, group=g).user.id == u.id assert not is_role_user(session, user=user) assert not is_role_user(session, group=Group.get(session, name="team-sre")) with pytest.raises(HTTPError): # Add token fe_url = url(base_url, '/users/{}/tokens/add'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'name': 'myDHDToken'}), headers={'X-Grouper-User': "******"}) # Add token fe_url = url(base_url, '/users/{}/tokens/add'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({'name': 'myDHDToken'}), headers={'X-Grouper-User': user.username}) assert resp.code == 200 # Verify add fe_url = url(base_url, '/users/{}'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="GET", headers={'X-Grouper-User': user.username}) assert resp.code == 200 assert "Added token: myDHDToken" in resp.body with pytest.raises(HTTPError): # Disable token fe_url = url(base_url, '/users/{}/tokens/1/disable'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body="", headers={'X-Grouper-User': "******"}) # Disable token fe_url = url(base_url, '/users/{}/tokens/1/disable'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="POST", body="", headers={'X-Grouper-User': user.username}) assert resp.code == 200 # Verify disable fe_url = url(base_url, '/users/{}'.format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="GET", headers={'X-Grouper-User': user.username}) assert resp.code == 200 assert "Disabled token: myDHDToken" in resp.body
def run(self, session, **kwargs): if kwargs.get("group"): Group.get_or_create(session, groupname=groupname) session.commit() elif kwargs.get("key") == "valuewith=": User.get_or_create(session, username=other_username) session.commit() else: User.get_or_create(session, username=username) session.commit()
def run(self, session, **kwargs): if kwargs.get('group'): Group.get_or_create(session, groupname=groupname) session.commit() elif kwargs.get('key') == 'valuewith=': User.get_or_create(session, username=other_username) session.commit() else: User.get_or_create(session, username=username) session.commit()
def test_group_name_checks(session, tmpdir, users, groups): # noqa: F811 username = "******" groupname = "team-sre" # check user/group name call_main(session, tmpdir, "group", "add_member", "--member", "invalid group name", username) assert (u"User", username) not in Group.get(session, name=groupname).my_members() bad_username = "******" call_main(session, tmpdir, "group", "add_member", "--member", groupname, bad_username) assert (u"User", bad_username) not in Group.get(session, name=groupname).my_members()
def test_group_bulk_add_remove(session, tmpdir, users, groups): # noqa: F811 groupname = "team-sre" # bulk add usernames = {"*****@*****.**", "*****@*****.**", "*****@*****.**"} call_main(session, tmpdir, "group", "add_member", "--member", groupname, *usernames) members = {u for _, u in Group.get(session, name=groupname).my_members()} assert usernames.issubset(members) # bulk remove call_main(session, tmpdir, "group", "remove_member", groupname, *usernames) members = {u for _, u in Group.get(session, name=groupname).my_members()} assert not members.intersection(usernames)
def test_group_disable_group_owner(get_plugin_proxy, session, tmpdir, users, groups): # noqa: F811 get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) username = "******" groupname = "team-sre" # add call_main(session, tmpdir, "group", "add_member", "--owner", groupname, username) assert (u"User", username) in Group.get(session, name=groupname).my_members() # disable (fails) call_main(session, tmpdir, "user", "disable", username) assert (u"User", username) in Group.get(session, name=groupname).my_members()
def test_group_name_checks(make_session, session, users, groups): make_session.return_value = session username = '******' groupname = 'team-sre' # check user/group name call_main('group', 'add_member', '--member', 'invalid group name', username) assert (u'User', username) not in Group.get(session, name=groupname).my_members() bad_username = '******' call_main('group', 'add_member', '--member', groupname, bad_username) assert (u'User', bad_username) not in Group.get(session, name=groupname).my_members()
def test_group_add_remove_member(session, tmpdir, users, groups): # noqa: F811 username = "******" groupname = "team-sre" # add assert (u"User", username) not in groups[groupname].my_members() call_main(session, tmpdir, "group", "add_member", "--member", groupname, username) all_members = Group.get(session, name=groupname).my_members() assert (u"User", username) in all_members _, _, _, role, _, _ = all_members[(u"User", username)] assert GROUP_EDGE_ROLES[role] == "member" # remove call_main(session, tmpdir, "group", "remove_member", groupname, username) assert (u"User", username) not in Group.get(session, name=groupname).my_members()
def test_group_bulk_add_remove(make_session, session, users, groups): make_session.return_value = session groupname = 'team-sre' # bulk add usernames = {'*****@*****.**', '*****@*****.**', '*****@*****.**'} call_main('group', 'add_member', '--member', groupname, *usernames) members = {u for _, u in Group.get(session, name=groupname).my_members().keys()} assert usernames.issubset(members) # bulk remove call_main('group', 'remove_member', groupname, *usernames) members = {u for _, u in Group.get(session, name=groupname).my_members().keys()} assert not members.intersection(usernames)
def get(self, *args, **kwargs): # type: (*Any, **Any) -> None group_id = kwargs.get("group_id") # type: Optional[int] name = kwargs.get("name") # type: Optional[str] group = Group.get(self.session, group_id, name) if not group: return self.notfound() # Only members can request permissions if not self.current_user.is_member(group.my_members()): return self.forbidden() args_by_perm = get_grantable_permissions( self.session, settings().restricted_ownership_permissions ) dropdown_form, text_form = GroupPermissionRequest._get_forms(args_by_perm, None) 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), dropdown_help=settings().permission_request_dropdown_help, text_help=settings().permission_request_text_help, )
def get_auditors_group(settings, session): # type: (Settings, Session) -> Group """Retrieve the group for auditors Arg(s): settings (settings): settings, to get the `auditors_group` name session (session): database session Return: Group object for the group for Grouper auditors, whose name is specified with `auditors_group` settings. Raise: Raise NoSuchGroup exception if either the name for the auditors group is not configured, or the group does not exist in the database. Raise GroupDoesNotHaveAuditPermission if the group does not actually have the PERMISSION_AUDITOR permission. """ # TODO: switch to exc.NoSuchGroup to remove one source dependency # on graph.py group_name = get_auditors_group_name(settings) if not group_name: raise NoSuchGroup("Please ask your admin to configure the `auditors_group` settings") group = Group.get(session, name=group_name) if not group: raise NoSuchGroup("Please ask your admin to configure the default group for auditors") if not any([p.name == PERMISSION_AUDITOR for p in group.my_permissions()]): raise GroupDoesNotHaveAuditPermission() return group
def get_role_user(session, user=None, group=None): # type: (Session, User, Group) -> RoleUser """ Takes in a User or a Group and returns a dictionary that contains all of the service account components for the service account that the user/group is part of. Args: session: the database session user: a User object to check group: a Group object to check Throws: RoleUserNotFound: if the user or group is not part of a service account Returns: a dictionary with all components of the service account of the user or group passed in """ if not is_role_user(session, user, group): raise RoleUserNotFound() if user: name = user.name else: assert group is not None name = group.name return RoleUser(User.get(session, name=name), Group.get(session, name=name))
def get(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() status = self.get_argument("status", None) offset = int(self.get_argument("offset", 0)) limit = int(self.get_argument("limit", 100)) if limit > 9000: limit = 9000 requests = get_requests_by_group(self.session, group, status=status).order_by( Request.requested_at.desc() ) members = group.my_members() total = requests.count() requests = requests.offset(offset).limit(limit) current_user_role = { 'is_owner': user_role_index(self.current_user, members) in OWNER_ROLE_INDICES, 'is_approver': user_role_index(self.current_user, members) in APPROVER_ROLE_INDICES, 'is_manager': user_role(self.current_user, members) == "manager", 'role': user_role(self.current_user, members), } self.render( "group-requests.html", group=group, requests=requests, members=members, status=status, statuses=REQUEST_STATUS_CHOICES, offset=offset, limit=limit, total=total, current_user_role=current_user_role, )
def get(self, request_id, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() members = group.my_members() my_role = user_role(self.current_user, members) if my_role not in ("manager", "owner", "np-owner"): return self.forbidden() request = self.session.query(Request).filter_by(id=request_id).scalar() if not request: return self.notfound() on_behalf = get_on_behalf_by_request(self.session, request) form = GroupRequestModifyForm(self.request.arguments) form.status.choices = self._get_choices(request.status) updates = request.my_status_updates() self.render( "group-request-update.html", group=group, request=request, on_behalf=on_behalf, members=members, form=form, statuses=REQUEST_STATUS_CHOICES, updates=updates )
def post(self, user_id=None, name=None): service_account = ServiceAccount.get(self.session, user_id, name) if not service_account: return self.notfound() if not self.check_access(self.session, self.current_user, service_account): return self.forbidden() form = self.get_form() if not form.validate(): return self.render( "service-account-enable.html", form=form, user=service_account.user, alerts=self.get_form_alerts(form.errors), ) owner = Group.get(self.session, name=form.data["owner"]) if owner is None: form.owner.errors.append("Group not found.") return self.render( "service-account-enable.html", form=form, user=service_account.user, alerts=self.get_form_alerts(form.errors), ) enable_service_account(self.session, self.current_user, service_account, owner) return self.redirect( "/groups/{}/service/{}?refresh=yes".format(owner.name, service_account.user.username) )
def test_group_disable_group_owner(user_make_session, group_make_session, get_plugin_proxy, session, users, groups): group_make_session.return_value = session user_make_session.return_value = session get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) username = '******' groupname = 'team-sre' # add call_main('group', 'add_member', '--owner', groupname, username) assert (u'User', username) in Group.get(session, name=groupname).my_members() # disable (fails) call_main('user', 'disable', username) assert (u'User', username) in Group.get(session, name=groupname).my_members()
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() members = group.my_members() if not user_role(self.current_user, members) in ("owner", "np-owner"): return self.forbidden() # Enabling and disabling service accounts via the group endpoints is forbidden # because we need the preserve_membership data that is only available via the # UserEnable form. if is_role_user(self.session, group=group): return self.forbidden() group.disable() self.session.commit() AuditLog.log(self.session, self.current_user.id, 'disable_group', 'Disabled group.', on_group_id=group.id) if group.audit: # complete the audit group.audit.complete = True self.session.commit() AuditLog.log(self.session, self.current_user.id, 'complete_audit', 'Disabling group completes group audit.', 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, mapping_id=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() if not self.check_access(self.session, self.current_user, service_account): return self.forbidden() mapping = ServiceAccountPermissionMap.get(self.session, mapping_id) if not mapping: return self.notfound() permission = mapping.permission argument = mapping.argument mapping.delete(self.session) Counter.incr(self.session, "updates") self.session.commit() AuditLog.log( self.session, self.current_user.id, "revoke_permission", "Revoked permission with argument: {}".format(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, 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() if not can_manage_service_account(self.session, service_account, self.current_user): return self.forbidden() form = ServiceAccountEditForm(self.request.arguments, obj=service_account) if not form.validate(): return self.render( "service-account-edit.html", service_account=service_account, group=group, form=form, alerts=self.get_form_alerts(form.errors) ) try: edit_service_account(self.session, self.current_user, service_account, form.data["description"], form.data["machine_set"]) except BadMachineSet as e: form.machine_set.errors.append(str(e)) return self.render( "service-account-edit.html", service_account=service_account, group=group, form=form, alerts=self.get_form_alerts(form.errors) ) return self.redirect("/groups/{}/service/{}".format( group.name, service_account.user.username))
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 test_oneoff(mock_make_session, mock_load_plugins, session): mock_make_session.return_value = session username = '******' other_username = '******' groupname = 'fake_group' class FakeOneOff(object): def configure(self, service_name): pass def run(self, session, **kwargs): if kwargs.get('group'): Group.get_or_create(session, groupname=groupname) session.commit() elif kwargs.get('key') == 'valuewith=': User.get_or_create(session, username=other_username) session.commit() else: User.get_or_create(session, username=username) session.commit() mock_load_plugins.return_value = [FakeOneOff()] # dry_run call_main('oneoff', 'run', 'FakeOneOff') assert User.get(session, name=username) is None, 'default dry_run means no writes' assert User.get(session, name=other_username) is None, '"valuewith= not in arg' assert Group.get(session, name=groupname) is None, '"group" not in arg so no group created' # not dry_run, create a user call_main('oneoff', 'run', '--no-dry_run', 'FakeOneOff') assert User.get(session, name=username) is not None, 'dry_run off means writes' assert User.get(session, name=other_username) is None, '"valuewith= not in arg' assert Group.get(session, name=groupname) is None, '"group" not in arg so no group created' # not dry_run, use kwarg to create a group call_main('oneoff', 'run', '--no-dry_run', 'FakeOneOff', 'group=1') assert User.get(session, name=username) is not None, 'dry_run off means writes' assert User.get(session, name=other_username) is None, '"valuewith= not in arg' assert Group.get(session, name=groupname) is not None, '"group" in arg so group created' # invalid format for argument should result in premature system exit with pytest.raises(SystemExit): call_main('oneoff', 'run', '--no-dry_run', 'FakeOneOff', 'bad_arg') call_main('oneoff', 'run', '--no-dry_run', 'FakeOneOff', 'key=valuewith=') assert User.get(session, name=other_username) is not None, '"valuewith= in arg, create user2'
def test_group_add_remove_owner(get_plugin_proxy, session, tmpdir, users, groups): # noqa: F811 get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) username = "******" groupname = "team-sre" # add assert (u"User", username) not in groups[groupname].my_members() call_main(session, tmpdir, "group", "add_member", "--owner", groupname, username) all_members = Group.get(session, name=groupname).my_members() assert (u"User", username) in all_members _, _, _, role, _, _ = all_members[(u"User", username)] assert GROUP_EDGE_ROLES[role] == "owner" # remove (fails) call_main(session, tmpdir, "group", "remove_member", groupname, username) assert (u"User", username) in Group.get(session, name=groupname).my_members()
def test_add_role_user(session, users, http_client, base_url): # noqa: F811 user = users["*****@*****.**"] # Add account create_role_user(session, user, "*****@*****.**", "Hi", "canjoin") u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_role_user(session, user=u) assert is_role_user(session, group=g) assert get_role_user(session, user=u).group.id == g.id assert get_role_user(session, group=g).user.id == u.id assert not is_role_user(session, user=user) assert not is_role_user(session, group=Group.get(session, name="team-sre"))
def test_oneoff(mock_load_plugins, session, tmpdir): # noqa: F811 username = "******" other_username = "******" groupname = "fake_group" class FakeOneOff(object): def configure(self, service_name): pass def run(self, session, **kwargs): if kwargs.get("group"): Group.get_or_create(session, groupname=groupname) session.commit() elif kwargs.get("key") == "valuewith=": User.get_or_create(session, username=other_username) session.commit() else: User.get_or_create(session, username=username) session.commit() mock_load_plugins.return_value = [FakeOneOff()] # dry_run call_main(session, tmpdir, "oneoff", "run", "FakeOneOff") assert User.get(session, name=username) is None, "default dry_run means no writes" assert User.get(session, name=other_username) is None, '"valuewith= not in arg' assert Group.get(session, name=groupname) is None, '"group" not in arg so no group created' # not dry_run, create a user call_main(session, tmpdir, "oneoff", "run", "--no-dry_run", "FakeOneOff") assert User.get(session, name=username) is not None, "dry_run off means writes" assert User.get(session, name=other_username) is None, '"valuewith= not in arg' assert Group.get(session, name=groupname) is None, '"group" not in arg so no group created' # not dry_run, use kwarg to create a group call_main(session, tmpdir, "oneoff", "run", "--no-dry_run", "FakeOneOff", "group=1") assert User.get(session, name=username) is not None, "dry_run off means writes" assert User.get(session, name=other_username) is None, '"valuewith= not in arg' assert Group.get(session, name=groupname) is not None, '"group" in arg so group created' # invalid format for argument should result in premature system exit with pytest.raises(SystemExit): call_main(session, tmpdir, "oneoff", "run", "--no-dry_run", "FakeOneOff", "bad_arg") call_main(session, tmpdir, "oneoff", "run", "--no-dry_run", "FakeOneOff", "key=valuewith=") assert User.get(session, name=other_username) is not None, '"valuewith= in arg, create user2'
def add_group_to_group(self, member, group, expiration=None): # type: (str, str, Optional[datetime]) -> None self.create_group(member) self.create_group(group) member_obj = Group.get(self.session, name=member) assert member_obj group_obj = Group.get(self.session, name=group) assert group_obj edge = GroupEdge( group_id=group_obj.id, member_type=OBJ_TYPES["Group"], member_pk=member_obj.id, expiration=expiration, active=True, _role=GROUP_EDGE_ROLES.index("member"), ) edge.add(self.session)
def test_group_add_remove_member(make_session, session, users, groups): make_session.return_value = session username = '******' groupname = 'team-sre' # add assert (u'User', username) not in groups[groupname].my_members() call_main('group', 'add_member', '--member', groupname, username) all_members = Group.get(session, name=groupname).my_members() assert (u'User', username) in all_members _, _, _, role, _, _ = all_members[(u'User', username)] assert GROUP_EDGE_ROLES[role] == "member" # remove call_main('group', 'remove_member', groupname, username) assert (u'User', username) not in Group.get(session, name=groupname).my_members()
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 test_add_role_user(session, users, http_client, base_url): user = users['*****@*****.**'] # Add account create_role_user(session, user, '*****@*****.**', 'Hi', 'canjoin') u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_role_user(session, user=u) assert is_role_user(session, group=g) assert get_role_user(session, user=u).group.id == g.id assert get_role_user(session, group=g).user.id == u.id assert not is_role_user(session, user=user) assert not is_role_user(session, group=Group.get(session, name="team-sre"))
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 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))
def sync_db_command(args): # Models not implicitly or explictly imported above are explicitly imported here from grouper.models.perf_profile import PerfProfile # noqa: F401 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 = get_permission(session, name) if test: continue try: create_permission(session, name, description) 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 = get_permission(session, permission_name) assert permission, "Permission should have been created earlier!" grant_permission(session, admin_group.id, permission.id) session.commit() auditors_group_name = get_auditors_group_name(settings) auditors_group = Group.get(session, name=auditors_group_name) if not auditors_group: auditors_group = Group( groupname=auditors_group_name, description= "Group for auditors, who can be owners of audited groups.", canjoin="canjoin", ) try: auditors_group.add(session) session.flush() except IntegrityError: session.rollback() raise Exception( "Failed to create group: {}".format(auditors_group_name)) permission = get_permission(session, PERMISSION_AUDITOR) assert permission, "Permission should have been created earlier!" grant_permission(session, auditors_group.id, permission.id) session.commit()
def _id_for_group(self, group): # type: (str) -> int group_obj = Group.get(self.session, name=group) if not group_obj: raise GroupNotFoundException(group) return group_obj.id
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() # Only members can request permissions if not self.current_user.is_member(group.my_members()): 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: request = 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("/permissions/requests/{}".format(request.id))
def post(self, request_id, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() members = group.my_members() my_role = user_role(self.current_user, members) if my_role not in ("manager", "owner", "np-owner"): return self.forbidden() request = self.session.query(Request).filter_by(id=request_id).scalar() if not request: return self.notfound() on_behalf = get_on_behalf_by_request(self.session, request) form = GroupRequestModifyForm(self.request.arguments) form.status.choices = self._get_choices(request.status) updates = request.my_status_updates() if not form.status.choices: alerts = [Alert("info", "Request has already been processed")] return self.render("group-request-update.html", group=group, request=request, on_behalf=on_behalf, members=members, form=form, alerts=alerts, statuses=REQUEST_STATUS_CHOICES, updates=updates) if not form.validate(): return self.render("group-request-update.html", group=group, request=request, on_behalf=on_behalf, members=members, form=form, alerts=self.get_form_alerts(form.errors), statuses=REQUEST_STATUS_CHOICES, updates=updates) # We have to test this here, too, to ensure that someone can't sneak in with a pending # request that used to be allowed. if form.data["status"] != "cancelled": fail_message = 'This join is denied with this role at this time.' try: user_can_join = assert_can_join( request.requesting, on_behalf, role=request.edge.role, ) except UserNotAuditor as e: user_can_join = False fail_message = e if not user_can_join: return self.render("group-request-update.html", group=group, request=request, on_behalf=on_behalf, members=members, form=form, statuses=REQUEST_STATUS_CHOICES, updates=updates, alerts=[ Alert('danger', fail_message, 'Audit Policy Enforcement') ]) request.update_status(self.current_user, form.data["status"], form.data["reason"]) self.session.commit() AuditLog.log(self.session, self.current_user.id, 'update_request', 'Updated request to status: {}'.format( form.data["status"]), on_group_id=group.id, on_user_id=request.requester.id) edge = self.session.query(GroupEdge).filter_by( id=request.edge_id).one() approver_mail_to = [ user.name for user in group.my_approver_users() if user.name != self.current_user.name and user.name != request.requester.username ] subj = "Re: " + self.render_template('email/pending_request_subj.tmpl', group=group.name, user=request.requester.username) send_email( self.session, approver_mail_to, subj, "approver_request_updated", settings, { 'group_name': group.name, 'requester': request.requester.username, 'changed_by': self.current_user.name, 'status': form.data['status'], 'role': edge.role, 'reason': form.data['reason'], 'references_header': request.reference_id, }, ) if form.data['status'] == 'actioned': send_email( self.session, [request.requester.name], 'Added to group: {}'.format(group.groupname), 'request_actioned', settings, { 'group_name': group.name, 'actioned_by': self.current_user.name, 'reason': form.data['reason'], 'expiration': edge.expiration, 'role': edge.role, }) elif form.data['status'] == 'cancelled': send_email( self.session, [request.requester.name], 'Request to join cancelled: {}'.format(group.groupname), 'request_cancelled', settings, { 'group_name': group.name, 'cancelled_by': self.current_user.name, 'reason': form.data['reason'], 'expiration': edge.expiration, 'role': edge.role, }) # No explicit refresh because handler queries SQL. if form.data['redirect_aggregate']: return self.redirect("/user/requests") else: return self.redirect("/groups/{}/requests".format(group.name))
def post(self, group_id=None, name=None, name2=None, member_type=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() if self.current_user.name == name2: return self.forbidden() members = group.my_members() my_role = user_role(self.current_user, members) if my_role not in ("manager", "owner", "np-owner"): return self.forbidden() member = members.get((member_type.capitalize(), name2), None) if not member: return self.notfound() if member.type == "Group": user_or_group = Group.get(self.session, member.id) else: user_or_group = User.get(self.session, member.id) if not user_or_group: return self.notfound() edge = GroupEdge.get( self.session, group_id=group.id, member_type=OBJ_TYPES[member.type], member_pk=member.id, ) if not edge: return self.notfound() form = GroupEditMemberForm(self.request.arguments) form.role.choices = [["member", "Member"]] if my_role in ("owner", "np-owner"): form.role.choices.append(["manager", "Manager"]) form.role.choices.append(["owner", "Owner"]) form.role.choices.append(["np-owner", "No-Permissions Owner"]) if not form.validate(): return self.render( "group-edit-member.html", group=group, member=member, edge=edge, form=form, alerts=self.get_form_alerts(form.errors), ) fail_message = 'This join is denied with this role at this time.' try: user_can_join = assert_can_join(group, user_or_group, role=form.data["role"]) except UserNotAuditor as e: user_can_join = False fail_message = e if not user_can_join: return self.render("group-edit-member.html", form=form, group=group, member=member, edge=edge, alerts=[ Alert('danger', fail_message, 'Audit Policy Enforcement') ]) expiration = None if form.data["expiration"]: expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y") try: group.edit_member(self.current_user, user_or_group, form.data["reason"], role=form.data["role"], expiration=expiration) except (InvalidRoleForMember, PluginRejectedGroupMembershipUpdate) as e: return self.render("group-edit-member.html", form=form, group=group, member=member, edge=edge, alerts=[Alert('danger', e.message)]) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def test_sa_tokens(session, users, http_client, base_url): # noqa: F811 user = users["*****@*****.**"] # Add account create_role_user(session, user, "*****@*****.**", "Hi", "canjoin") u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_role_user(session, user=u) assert is_role_user(session, group=g) assert get_role_user(session, user=u).group.id == g.id assert get_role_user(session, group=g).user.id == u.id assert not is_role_user(session, user=user) assert not is_role_user(session, group=Group.get(session, name="team-sre")) with pytest.raises(HTTPError): # Add token fe_url = url(base_url, "/users/{}/tokens/add".format("*****@*****.**")) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"name": "myDHDToken"}), headers={"X-Grouper-User": "******"}, ) # Add token fe_url = url(base_url, "/users/{}/tokens/add".format("*****@*****.**")) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"name": "myDHDToken"}), headers={"X-Grouper-User": user.username}, ) assert resp.code == 200 # Verify add fe_url = url(base_url, "/users/{}".format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="GET", headers={"X-Grouper-User": user.username}) assert resp.code == 200 assert b"Added token: myDHDToken" in resp.body with pytest.raises(HTTPError): # Disable token fe_url = url(base_url, "/users/{}/tokens/1/disable".format("*****@*****.**")) resp = yield http_client.fetch( fe_url, method="POST", body="", headers={"X-Grouper-User": "******"} ) # Disable token fe_url = url(base_url, "/users/{}/tokens/1/disable".format("*****@*****.**")) resp = yield http_client.fetch( fe_url, method="POST", body="", headers={"X-Grouper-User": user.username} ) assert resp.code == 200 # Verify disable fe_url = url(base_url, "/users/{}".format("*****@*****.**")) resp = yield http_client.fetch(fe_url, method="GET", headers={"X-Grouper-User": user.username}) assert resp.code == 200 assert b"Disabled token: myDHDToken" in resp.body
def test_service_accounts(setup): # type: (SetupTest) -> None """Tests remaining non-usecase service account functions.""" with setup.transaction(): setup.create_service_account("*****@*****.**", "team-sre", "some machines", "some service account") setup.add_user_to_group("*****@*****.**", "team-sre", "owner") setup.add_user_to_group("*****@*****.**", "admins") setup.grant_permission_to_group(USER_ADMIN, "", "admins") setup.add_user_to_group("*****@*****.**", "team-sre") setup.create_user("*****@*****.**") setup.grant_permission_to_service_account("team-sre", "*", "*****@*****.**") setup.create_group("security-team") group = Group.get(setup.session, name="team-sre") assert group accounts = get_service_accounts(setup.session, group) assert len(accounts) == 1 service_account = accounts[0] assert service_account.user.name == "*****@*****.**" assert service_account.user.is_service_account # zorkian should be able to manage the account, as should gary, but oliver (not a member of the # group) should not. zorkian_user = User.get(setup.session, name="*****@*****.**") assert zorkian_user gary_user = User.get(setup.session, name="*****@*****.**") assert gary_user oliver_user = User.get(setup.session, name="*****@*****.**") assert oliver_user assert can_manage_service_account(setup.session, service_account, zorkian_user) assert can_manage_service_account(setup.session, service_account, gary_user) assert not can_manage_service_account(setup.session, service_account, oliver_user) # Diabling the service account should remove the link to the group. disable_service_account(setup.session, zorkian_user, service_account) assert service_account.user.enabled == False assert get_service_accounts(setup.session, group) == [] # The user should also be gone from the graph and have its permissions removed. setup.graph.update_from_db(setup.session) group_details = setup.graph.get_group_details("team-sre") assert "service_accounts" not in group_details metadata = setup.graph.user_metadata["*****@*****.**"] assert not metadata["enabled"] assert "owner" not in metadata["service_account"] user_details = setup.graph.get_user_details("*****@*****.**") assert user_details["permissions"] == [] # We can re-enable and attach to a different group. new_group = Group.get(setup.session, name="security-team") assert new_group enable_service_account(setup.session, zorkian_user, service_account, new_group) assert service_account.user.enabled == True assert get_service_accounts(setup.session, group) == [] accounts = get_service_accounts(setup.session, new_group) assert len(accounts) == 1 assert accounts[0].user.name == "*****@*****.**" # Check that this is reflected in the graph and the user has no permissions. setup.graph.update_from_db(setup.session) group_details = setup.graph.get_group_details("security-team") assert group_details["service_accounts"] == ["*****@*****.**"] metadata = setup.graph.user_metadata["*****@*****.**"] assert metadata["service_account"]["owner"] == "security-team" user_details = setup.graph.get_user_details("*****@*****.**") assert user_details["permissions"] == []
def disable_group(self, group): # type: (str) -> None group_obj = Group.get(self.session, name=group) assert group_obj group_obj.enabled = False
def post(self, *args, **kwargs): # type: (*Any, **Any) -> None request_id = kwargs["request_id"] # type: int group_id = kwargs.get("group_id") # type: Optional[int] name = kwargs.get("name") # type: Optional[str] group = Group.get(self.session, group_id, name) if not group: return self.notfound() members = group.my_members() my_role = user_role(self.current_user, members) if my_role not in ("manager", "owner", "np-owner"): return self.forbidden() request = self.session.query(Request).filter_by(id=request_id).scalar() if not request: return self.notfound() on_behalf = get_on_behalf_by_request(self.session, request) form = GroupRequestModifyForm(self.request.arguments) form.status.choices = self._get_choices(request.status) updates = request.my_status_updates() if not form.status.choices: alerts = [Alert("info", "Request has already been processed")] return self.render( "group-request-update.html", group=group, request=request, on_behalf=on_behalf, members=members, form=form, alerts=alerts, statuses=REQUEST_STATUS_CHOICES, updates=updates, ) if not form.validate(): return self.render( "group-request-update.html", group=group, request=request, on_behalf=on_behalf, members=members, form=form, alerts=self.get_form_alerts(form.errors), statuses=REQUEST_STATUS_CHOICES, updates=updates, ) # We have to test this here, too, to ensure that someone can't sneak in with a pending # request that used to be allowed. if form.data["status"] != "cancelled": fail_message = "This join is denied with this role at this time." try: user_can_join = assert_can_join( request.requesting, on_behalf, role=request.edge.role ) except UserNotAuditor as e: user_can_join = False fail_message = str(e) if not user_can_join: return self.render( "group-request-update.html", group=group, request=request, on_behalf=on_behalf, members=members, form=form, statuses=REQUEST_STATUS_CHOICES, updates=updates, alerts=[Alert("danger", fail_message, "Audit Policy Enforcement")], ) request.update_status(self.current_user, form.data["status"], form.data["reason"]) self.session.commit() AuditLog.log( self.session, self.current_user.id, "update_request", "Updated request to status: {}".format(form.data["status"]), on_group_id=group.id, on_user_id=request.requester.id, ) edge = self.session.query(GroupEdge).filter_by(id=request.edge_id).one() approver_mail_to = [ user.name for user in group.my_approver_users() if user.name != self.current_user.name and user.name != request.requester.username ] subj = "Re: " + self.render_template( "email/pending_request_subj.tmpl", group=group.name, user=request.requester.username ) send_email( self.session, approver_mail_to, subj, "approver_request_updated", settings(), { "group_name": group.name, "requester": request.requester.username, "changed_by": self.current_user.name, "status": form.data["status"], "role": edge.role, "reason": form.data["reason"], "references_header": request.reference_id, }, ) if form.data["status"] == "actioned": send_email( self.session, [request.requester.name], "Added to group: {}".format(group.groupname), "request_actioned", settings(), { "group_name": group.name, "actioned_by": self.current_user.name, "reason": form.data["reason"], "expiration": edge.expiration, "role": edge.role, }, ) elif form.data["status"] == "cancelled": send_email( self.session, [request.requester.name], "Request to join cancelled: {}".format(group.groupname), "request_cancelled", settings(), { "group_name": group.name, "cancelled_by": self.current_user.name, "reason": form.data["reason"], "expiration": edge.expiration, "role": edge.role, }, ) # No explicit refresh because handler queries SQL. if form.data["redirect_aggregate"]: return self.redirect("/user/requests") else: return self.redirect("/groups/{}/requests".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() if not user_can_manage_group(self.session, group, self.current_user): return self.forbidden() members = group.my_members() my_role = user_role(self.current_user, members) form = self.get_form(role=my_role) if not form.validate(): return self.render("group-add.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)) member = get_user_or_group(self.session, form.data["member"]) if member.type == "User" and is_role_user(self.session, member): # For service accounts, we want to always add the group to other groups, not the user member = get_role_user(self.session, user=member).group if not member: form.member.errors.append("User or group not found.") elif (member.type, member.name) in group.my_members(): form.member.errors.append( "User or group is already a member of this group.") elif group.name == member.name: form.member.errors.append( "By definition, this group is a member of itself already.") # Ensure this doesn't violate auditing constraints fail_message = 'This join is denied with this role at this time.' try: user_can_join = assert_can_join(group, member, role=form.data["role"]) except UserNotAuditor as e: user_can_join = False fail_message = e if not user_can_join: form.member.errors.append(fail_message) if form.member.errors: return self.render("group-add.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)) expiration = None if form.data["expiration"]: expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y") try: group.add_member(requester=self.current_user, user_or_group=member, reason=form.data["reason"], status='actioned', expiration=expiration, role=form.data["role"]) except InvalidRoleForMember as e: return self.render("group-add.html", form=form, group=group, alerts=[Alert('danger', e.message)]) self.session.commit() on_user_id = member.id if member.type == "User" else None AuditLog.log(self.session, self.current_user.id, 'join_group', '{} added to group with role: {}'.format( member.name, form.data["role"]), on_group_id=group.id, on_user_id=on_user_id) if member.type == "User": send_email( self.session, [member.name], 'Added to group: {}'.format(group.name), 'request_actioned', settings, { 'group_name': group.name, 'actioned_by': self.current_user.name, 'reason': form.data['reason'], 'expiration': expiration, 'role': form.data['role'], }) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def test_grant_and_revoke( session, standard_graph, graph, groups, permissions, http_client, base_url # noqa: F811 ): """Test that permission grant and revokes are reflected correctly.""" group_name = "team-sre" permission_name = "sudo" user_name = "*****@*****.**" def _check_graph_for_perm(graph): # type: (GroupGraph) -> bool return any([ g["permission"] == permission_name and g["distance"] == 0 for g in graph.get_group_details(group_name)["permissions"] ]) # make some permission admins grant_permission(groups["security-team"], permissions[PERMISSION_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 = get_permission(session, 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 post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() if not user_can_manage_group(self.session, group, self.current_user): return self.forbidden() form = GroupRemoveForm(self.request.arguments) if not form.validate(): return self.send_error(status_code=400) member_type, member_name = form.data["member_type"], form.data[ "member"] members = group.my_members() if not members.get((member_type.capitalize(), member_name), None): return self.notfound() removed_member = get_user_or_group(self.session, member_name, user_or_group=member_type) if self.current_user == removed_member: return self.send_error( status_code=400, reason="Can't remove yourself. Leave group instead.") role_user = is_role_user(self.session, group=group) if role_user and get_role_user( self.session, group=group).user.name == removed_member.name: return self.send_error( status_code=400, reason= "Can't remove a service account user from the service account group.", ) try: group.revoke_member(self.current_user, removed_member, "Removed by owner/np-owner/manager") AuditLog.log( self.session, self.current_user.id, "remove_from_group", "{} was removed from the group.".format(removed_member.name), on_group_id=group.id, on_user_id=removed_member.id, ) except PluginRejectedGroupMembershipUpdate as e: alert = Alert("danger", str(e)) if role_user: return self.redirect("/service/{}".format(group.name), alerts=[alert]) else: return self.redirect("/groups/{}".format(group.name), alerts=[alert]) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def post(self): form = AuditCreateForm(self.request.arguments) if not form.validate(): return self.render("audit-create.html", form=form, alerts=self.get_form_alerts(form.errors)) user = self.get_current_user() if not user_has_permission(self.session, user, AUDIT_MANAGER): return self.forbidden() # Step 1, detect if there are non-completed audits and fail if so. open_audits = self.session.query(Audit).filter( Audit.complete == False).all() if open_audits: raise Exception("Sorry, there are audits in progress.") ends_at = datetime.strptime(form.data["ends_at"], "%m/%d/%Y") # Step 2, find all audited groups and schedule audits for each. audited_groups = [] for groupname in self.graph.groups: if not self.graph.get_group_details(groupname)["audited"]: continue group = Group.get(self.session, name=groupname) audit = Audit(group_id=group.id, ends_at=ends_at) try: audit.add(self.session) self.session.flush() except IntegrityError: self.session.rollback() raise Exception("Failed to start the audit. Please try again.") # Update group with new audit audited_groups.append(group) group.audit_id = audit.id # Step 3, now get all members of this group and set up audit rows for those edges. for member in group.my_members().values(): auditmember = AuditMember(audit_id=audit.id, edge_id=member.edge_id) try: auditmember.add(self.session) except IntegrityError: self.session.rollback() raise Exception( "Failed to start the audit. Please try again.") self.session.commit() AuditLog.log( self.session, self.current_user.id, "start_audit", "Started global audit.", category=AuditLogCategory.audit, ) # Calculate schedule of emails, basically we send emails at various periods in advance # of the end of the audit period. schedule_times = [] not_before = datetime.utcnow() + timedelta(1) for days_prior in (28, 21, 14, 7, 3, 1): email_time = ends_at - timedelta(days_prior) email_time.replace(hour=17, minute=0, second=0) if email_time > not_before: schedule_times.append((days_prior, email_time)) # Now send some emails. We do this separately/later to ensure that the audits are all # created. Email notifications are sent multiple times if group audits are still # outstanding. for group in audited_groups: mail_to = [ member.name for member in group.my_users() if GROUP_EDGE_ROLES[member.role] in ("owner", "np-owner") ] send_email( self.session, mail_to, "Group Audit: {}".format(group.name), "audit_notice", settings, { "group": group.name, "ends_at": ends_at }, ) for days_prior, email_time in schedule_times: send_async_email( self.session, mail_to, "Group Audit: {} - {} day(s) left".format( group.name, days_prior), "audit_notice_reminder", settings, { "group": group.name, "ends_at": ends_at, "days_left": days_prior }, email_time, async_key="audit-{}".format(group.id), ) return self.redirect("/audits")
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 get(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() form = ServiceAccountCreateForm() return self.render("service-account-create.html", form=form, group=group)
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() form = GroupJoinForm(self.request.arguments) form.member.choices = self._get_choices(group) if not form.validate(): return self.render("group-join.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)) member = self._get_member(form.data["member"]) fail_message = 'This join is denied with this role at this time.' try: user_can_join = assert_can_join(group, member, role=form.data["role"]) except UserNotAuditor as e: user_can_join = False fail_message = e if not user_can_join: return self.render("group-join.html", form=form, group=group, alerts=[ Alert('danger', fail_message, 'Audit Policy Enforcement') ]) if group.canjoin == "nobody": fail_message = 'This group cannot be joined at this time.' return self.render("group-join.html", form=form, group=group, alerts=[Alert('danger', fail_message)]) # We only use the default expiration time if no expiration time was given # This does mean that if a user wishes to join a group with no expiration # (even with an owner's permission) that has an auto expiration, they must # first be accepted to the group and then have the owner edit the user to # have no expiration. expiration = None if form.data["expiration"]: expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y") elif group.auto_expire: expiration = datetime.utcnow() + group.auto_expire request = group.add_member(requester=self.current_user, user_or_group=member, reason=form.data["reason"], status=GROUP_JOIN_CHOICES[group.canjoin], expiration=expiration, role=form.data["role"]) self.session.commit() if group.canjoin == 'canask': AuditLog.log(self.session, self.current_user.id, 'join_group', '{} requested to join with role: {}'.format( member.name, form.data["role"]), on_group_id=group.id) mail_to = [ user.name for user in group.my_users() if GROUP_EDGE_ROLES[user.role] in ('manager', 'owner', 'np-owner') ] email_context = { "requester": member.name, "requested_by": self.current_user.name, "request_id": request.id, "group_name": group.name, "reason": form.data["reason"], "expiration": expiration, "role": form.data["role"], "references_header": request.reference_id, } subj = self.render_template('email/pending_request_subj.tmpl', group=group.name, user=self.current_user.name) send_email(self.session, mail_to, subj, 'pending_request', settings, email_context) elif group.canjoin == 'canjoin': AuditLog.log(self.session, self.current_user.id, 'join_group', '{} auto-approved to join with role: {}'.format( member.name, form.data["role"]), on_group_id=group.id) else: raise Exception('Need to update the GroupJoin.post audit logging') 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, **kwargs): # type: (*Any, **Any) -> None group_id = kwargs.get("group_id") # type: Optional[int] name = kwargs.get("name") # type: Optional[str] group = Group.get(self.session, group_id, name) if not group or not group.enabled: return self.notfound() members = group.my_members() member_groups = {g for t, g in members if t == "Group"} user_is_member = self._is_user_a_member(group, members) form = GroupJoinForm(self.request.arguments) form.member.choices = self._get_choices(group, member_groups, user_is_member) if not form.validate(): return self.render("group-join.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)) member = self._get_member(form.data["member"]) if not member: return self.render( "group-join.html", form=form, group=group, alerts=[ Alert( "danger", "Unknown user or group: {}".format( form.data["member"])) ], ) fail_message = "This join is denied with this role at this time." try: user_can_join = assert_can_join(group, member, role=form.data["role"]) except UserNotAuditor as e: user_can_join = False fail_message = str(e) if not user_can_join: return self.render( "group-join.html", form=form, group=group, alerts=[ Alert("danger", fail_message, "Audit Policy Enforcement") ], ) if group.canjoin == "nobody": fail_message = "This group cannot be joined at this time." return self.render("group-join.html", form=form, group=group, alerts=[Alert("danger", fail_message)]) if group.require_clickthru_tojoin: if not form.data["clickthru_agreement"]: return self.render( "group-join.html", form=form, group=group, alerts=[ Alert( "danger", "please accept review of the group's description", "Clickthru Enforcement", ) ], ) # We only use the default expiration time if no expiration time was given # This does mean that if a user wishes to join a group with no expiration # (even with an owner's permission) that has an auto expiration, they must # first be accepted to the group and then have the owner edit the user to # have no expiration. expiration = None if form.data["expiration"]: expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y") elif group.auto_expire: expiration = datetime.utcnow() + group.auto_expire request = group.add_member( requester=self.current_user, user_or_group=member, reason=form.data["reason"], status=GROUP_JOIN_CHOICES[group.canjoin], expiration=expiration, role=form.data["role"], ) self.session.commit() if group.canjoin == "canask": AuditLog.log( self.session, self.current_user.id, "join_group", "{} requested to join with role: {}".format( member.name, form.data["role"]), on_group_id=group.id, ) mail_to = [ user.name for user in group.my_users() if GROUP_EDGE_ROLES[user.role] in ("manager", "owner", "np-owner") ] email_context = { "requester": member.name, "requested_by": self.current_user.name, "request_id": request.id, "group_name": group.name, "reason": form.data["reason"], "expiration": expiration, "role": form.data["role"], "references_header": request.reference_id, } subj = self.render_template("email/pending_request_subj.tmpl", group=group.name, user=self.current_user.name) send_email(self.session, mail_to, subj, "pending_request", settings(), email_context) elif group.canjoin == "canjoin": AuditLog.log( self.session, self.current_user.id, "join_group", "{} auto-approved to join with role: {}".format( member.name, form.data["role"]), on_group_id=group.id, ) else: raise Exception("Need to update the GroupJoin.post audit logging") return self.redirect("/groups/{}?refresh=yes".format(group.name))
def notify_edge_expiration(settings: Settings, session: Session, edge: GroupEdge) -> None: """Send notification that an edge has expired. Handles email notification and audit logging. Args: settings: Grouper Settings object for current run. session: Object for db session. edge: The expiring edge. """ # TODO(herb): get around circular depdendencies; long term remove call to # send_async_email() from grouper.models from grouper.models.group import Group # Pull data about the edge and the affected user or group. # # TODO(rra): The audit log currently has no way of representing a system action. Everything # must be attributed to a user. When expiring a user, use the user themselves as the actor for # the audit log entry. When expiring a group, use an arbitrary owner of the group from which # they are expiring or, if that fails, an arbitrary owner of the group whose membership is # expiring. If neither group has an owner, raise an exception. This can all go away once the # audit log has a mechanism for recording system actions. group_name = edge.group.name if OBJ_TYPES_IDX[edge.member_type] == "User": user = User.get(session, pk=edge.member_pk) assert user actor_id = user.id member_name = user.username recipients = [member_name] member_is_user = True else: subgroup = Group.get(session, pk=edge.member_pk) parent_owners = edge.group.my_owners() if parent_owners: actor_id = list(parent_owners.values())[0].id else: child_owners = subgroup.my_owners() if child_owners: actor_id = list(child_owners.values())[0].id else: msg = "{} and {} both have no owners during expiration of {}'s membership".format( group_name, subgroup.groupname, subgroup.groupname) raise UnknownActorDuringExpirationException(msg) member_name = subgroup.groupname recipients = subgroup.my_owners_as_strings() member_is_user = False # Log to the audit log. How depends on whether a user's membership has expired or a group's # membership has expired. audit_data = { "action": "expired_from_group", "actor_id": actor_id, "description": "{} expired out of the group".format(member_name), } if member_is_user: assert user AuditLog.log(session, on_user_id=user.id, on_group_id=edge.group_id, **audit_data) else: # Make an audit log entry for both the subgroup and the parent group so that it will show # up in the FE view for both groups. AuditLog.log(session, on_group_id=edge.group_id, **audit_data) AuditLog.log(session, on_group_id=subgroup.id, **audit_data) # Send email notification to the affected people. email_context = { "group_name": group_name, "member_name": member_name, "member_is_user": member_is_user, } send_email( session=session, recipients=recipients, subject="Membership in {} expired".format(group_name), template="expiration", settings=settings, context=email_context, )
def post(self, *args, **kwargs): # type: (*Any, **Any) -> None form, args_by_perm = self._build_form(self.request.arguments) if not form.validate(): return self.render( "permission-request.html", args_by_perm_json=json.dumps(args_by_perm), form=form, alerts=self.get_form_alerts(form.errors), ) group = Group.get(self.session, name=form.group_name.data) if group is None: raise HTTPError(status_code=400, reason="that group does not exist") permission = get_permission(self.session, form.permission_name.data) if permission is None: raise HTTPError(status_code=400, reason="that permission does not exist") if permission.name not in args_by_perm: raise HTTPError(status_code=400, reason="that permission was not in the form") # save off request try: request = 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.", ) ] except UserNotAuditor as e: alerts = [Alert("danger", str(e))] else: alerts = [] if alerts: return self.render( "permission-request.html", args_by_perm_json=json.dumps(args_by_perm), form=form, alerts=alerts, ) else: return self.redirect("/permissions/requests/{}".format(request.id))
def test_disable_role_user(session, users, http_client, base_url): # noqa: F811 user = users["*****@*****.**"] # Add account create_role_user(session, user, "*****@*****.**", "Hi", "canjoin") u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_role_user(session, user=u) assert is_role_user(session, group=g) assert get_role_user(session, user=u).group.id == g.id assert get_role_user(session, group=g).user.id == u.id assert not is_role_user(session, user=user) assert not is_role_user(session, group=Group.get(session, name="team-sre")) disable_role_user(session, user=u) u = User.get(session, name="*****@*****.**") assert not u.enabled, "The SA User should be disabled" g = Group.get(session, name="*****@*****.**") assert not g.enabled, "The SA Group should be disabled" enable_role_user(session, actor=user, group=g, preserve_membership=True) u = User.get(session, name="*****@*****.**") assert u.enabled, "The SA User should be enabled" g = Group.get(session, name="*****@*****.**") assert g.enabled, "The SA Group should be enabled" with pytest.raises(HTTPError): fe_url = url(base_url, "/groups/{}/disable".format("*****@*****.**")) yield http_client.fetch( fe_url, method="POST", body="", headers={"X-Grouper-User": user.username} ) u = User.get(session, name="*****@*****.**") assert u.enabled, "Attempting to disable SAs through groups/disable should not work" g = Group.get(session, name="*****@*****.**") assert g.enabled, "Attempting to disable SAs through groups/disable should not work" fe_url = url(base_url, "/users/{}/disable".format("*****@*****.**")) yield http_client.fetch( fe_url, method="POST", body="", headers={"X-Grouper-User": user.username} ) u = User.get(session, name="*****@*****.**") assert not u.enabled, "The SA User should be disabled" g = Group.get(session, name="*****@*****.**") assert not g.enabled, "The SA Group should be disabled" with pytest.raises(HTTPError): fe_url = url(base_url, "/groups/{}/enable".format("*****@*****.**")) yield http_client.fetch( fe_url, method="POST", body="", headers={"X-Grouper-User": user.username} ) u = User.get(session, name="*****@*****.**") assert not u.enabled, "Attempting to enable SAs through groups/enable should not work" g = Group.get(session, name="*****@*****.**") assert not g.enabled, "Attempting to enable SAs through groups/enable should not work"
def _load_permissions_by_group_name(session, group_name): # noqa: F811 group = Group.get(session, name=group_name) return [name for _, name, _, _, _ in group.my_permissions()]
def test_auditor_promotion(mock_nnp, mock_gagn, session, graph, permissions, users): # noqa: F811 """Test automatic promotion of non-auditor approvers We set up our own group/user/permission for testing instead of using the `standard_graph` fixture---retrofitting it to work for us and also not break existing tests is too cumbersome. So here are our groups: very-special-auditors: * user14 group-1: * user11 (o) * user12 * user13 (np-o) * user14 (o, a) group-2: * user13 (np-o) * user21 (o) * user22 group-3: * user22 (o) * user12 (o) group-4: * user21 (np-o) * user41 * user42 (o) * user43 (np-o) o: owner, np-o: no-permission owner, a: auditor group-1 and group-2 have the permission that we will enable auditing. group-4 will be a subgroup of group-1 and thus will inherit the audited permission from group-1. The expected outcome is: user11, user13, user21, user42, and user43 will be added to the auditors group. """ settings = BackgroundSettings() set_global_settings(settings) # # set up our test part of the graph # # create groups AUDITED_GROUP = "audited" AUDITORS_GROUP = mock_gagn.return_value = "very-special-auditors" PERMISSION_NAME = "test-permission" all_groups = { groupname: Group.get_or_create(session, groupname=groupname)[0] for groupname in ("group-1", "group-2", "group-3", "group-4", AUDITORS_GROUP) } # create users users.update({ username + "@a.co": User.get_or_create(session, username=username + "@a.co")[0] for username in ( "user11", "user12", "user13", "user14", "user21", "user22", "user23", "user41", "user42", "user43", ) }) # create permissions permissions.update({ permission: get_or_create_permission( session, permission, description="{} permission".format(permission))[0] for permission in [PERMISSION_NAME] }) # add users to groups for (groupname, username, role) in ( ("group-1", "user11", "owner"), ("group-1", "user12", "member"), ("group-1", "user13", "np-owner"), ("group-1", "user14", "owner"), ("group-2", "user13", "np-owner"), ("group-2", "user21", "owner"), ("group-2", "user22", "member"), ("group-3", "user12", "owner"), ("group-3", "user22", "owner"), ("group-4", "user21", "np-owner"), ("group-4", "user41", "member"), ("group-4", "user42", "owner"), ("group-4", "user43", "np-owner"), ): add_member(all_groups[groupname], users[username + "@a.co"], role=role) # add group-4 as member of group-1 add_member(all_groups["group-1"], all_groups["group-4"]) # add user14 to auditors group add_member(all_groups[AUDITORS_GROUP], users["*****@*****.**"]) # grant permissions to groups # # give the test permission to groups 1 and 2, and group 4 should # also inherit from group 1 grant_permission(all_groups["group-1"], permissions[PERMISSION_NAME]) grant_permission(all_groups["group-2"], permissions[PERMISSION_NAME]) grant_permission(all_groups[AUDITORS_GROUP], permissions[PERMISSION_AUDITOR]) graph.update_from_db(session) # done setting up # now a few pre-op checks assert not graph.get_group_details("group-1").get(AUDITED_GROUP) assert not graph.get_group_details("group-4").get(AUDITED_GROUP) assert get_users(graph, AUDITORS_GROUP) == set(["*****@*****.**"]) assert get_users(graph, "group-3") == set(["*****@*****.**", "*****@*****.**"]) # # run the promotion logic -> nothing should happen because the # test-permission is not yet audited # background = BackgroundProcessor(settings, None) background.promote_nonauditors(session) graph.update_from_db(session) # nothing should have happened assert not graph.get_group_details("group-1").get(AUDITED_GROUP) assert not graph.get_group_details("group-4").get(AUDITED_GROUP) assert get_users(graph, AUDITORS_GROUP) == set(["*****@*****.**"]) assert mock_nnp.call_count == 0 # # now enable auditing for the permission and run the promotion # logic again # enable_permission_auditing(session, PERMISSION_NAME, users["*****@*****.**"].id) graph.update_from_db(session) assert graph.get_group_details("group-1").get(AUDITED_GROUP) assert graph.get_group_details("group-4").get(AUDITED_GROUP) background = BackgroundProcessor(settings, None) background.promote_nonauditors(session) graph.update_from_db(session) # check that stuff happened assert get_users(graph, AUDITORS_GROUP) == set([ "*****@*****.**", "*****@*****.**", "*****@*****.**", "*****@*****.**", "*****@*****.**", "*****@*****.**" ]) expected_calls = [ call(settings, session, users["*****@*****.**"], all_groups[AUDITORS_GROUP], set(["group-1"])), call( settings, session, users["*****@*****.**"], all_groups[AUDITORS_GROUP], set(["group-1", "group-2"]), ), call( settings, session, users["*****@*****.**"], all_groups[AUDITORS_GROUP], set(["group-2", "group-4"]), ), call(settings, session, users["*****@*****.**"], all_groups[AUDITORS_GROUP], set(["group-4"])), call(settings, session, users["*****@*****.**"], all_groups[AUDITORS_GROUP], set(["group-4"])), ] assert mock_nnp.call_count == len(expected_calls) mock_nnp.assert_has_calls(expected_calls, any_order=True) # # run the background promotion logic again, and nothing should # happen # mock_nnp.reset_mock() background = BackgroundProcessor(settings, None) background.promote_nonauditors(session) assert mock_nnp.call_count == 0
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") group = Group.get(self.session, name=name) if not group or not group.enabled: return self.notfound() members = group.my_members() member_groups = {g for t, g in members if t == "Group"} user_is_member = self._is_user_a_member(group, members) form = GroupJoinForm(self.request.arguments) form.member.choices = self._get_choices(group, member_groups, user_is_member) if not form.validate(): return self.render("group-join.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)) member = self._get_member(form.data["member"]) if not member: return self.render( "group-join.html", form=form, group=group, alerts=[ Alert( "danger", "Unknown user or group: {}".format( form.data["member"])) ], ) try: assert_can_join(group, member, role=form.data["role"]) except UserNotAuditor as e: return self.render( "group-join.html", form=form, group=group, alerts=[Alert("danger", str(e), "Audit Policy Enforcement")], ) if group.canjoin == "nobody": fail_message = "This group cannot be joined at this time." return self.render("group-join.html", form=form, group=group, alerts=[Alert("danger", fail_message)]) if group.require_clickthru_tojoin: if not form.data["clickthru_agreement"]: return self.render( "group-join.html", form=form, group=group, alerts=[ Alert( "danger", "please accept review of the group's description", "Clickthru Enforcement", ) ], ) # We only use the default expiration time if no expiration time was given # This does mean that if a user wishes to join a group with no expiration # (even with an owner's permission) that has an auto expiration, they must # first be accepted to the group and then have the owner edit the user to # have no expiration. expiration = None if form.data["expiration"]: expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y") elif group.auto_expire: expiration = datetime.utcnow() + group.auto_expire # If the requested role is member, set the status based on the group's canjoin setting, # which automatically actions the request if the group can be joined by anyone and # otherwise sets it pending. # # However, we don't want to let people autojoin as owner or np-owner even to otherwise open # groups, so if the role is not member, force the status to pending. if form.data["role"] == "member": status = GROUP_JOIN_CHOICES[group.canjoin] else: status = "pending" try: request = group.add_member( requester=self.current_user, user_or_group=member, reason=form.data["reason"], status=status, expiration=expiration, role=form.data["role"], ) except InvalidRoleForMember as e: return self.render( "group-join.html", form=form, group=group, alerts=[Alert("danger", str(e), "Invalid Role")], ) self.session.commit() if status == "pending": AuditLog.log( self.session, self.current_user.id, "join_group", "{} requested to join with role: {}".format( member.name, form.data["role"]), on_group_id=group.id, ) mail_to = [ user.name for user in group.my_users() if GROUP_EDGE_ROLES[user.role] in ("manager", "owner", "np-owner") ] email_context = { "requester": member.name, "requested_by": self.current_user.name, "request_id": request.id, "group_name": group.name, "reason": form.data["reason"], "expiration": expiration, "role": form.data["role"], "references_header": request.reference_id, } subj = self.render_template("email/pending_request_subj.tmpl", group=group.name, user=self.current_user.name) send_email(self.session, mail_to, subj, "pending_request", settings(), email_context) elif status == "actioned": AuditLog.log( self.session, self.current_user.id, "join_group", "{} auto-approved to join with role: {}".format( member.name, form.data["role"]), on_group_id=group.id, ) else: raise Exception(f"Unknown join status {status}") return self.redirect("/groups/{}?refresh=yes".format(group.name))
def test_oneoff(mock_load_plugins, session, tmpdir): # noqa: F811 username = "******" other_username = "******" groupname = "fake_group" class FakeOneOff(object): def configure(self, service_name): pass def run(self, session, **kwargs): if kwargs.get("group"): Group.get_or_create(session, groupname=groupname) session.commit() elif kwargs.get("key") == "valuewith=": User.get_or_create(session, username=other_username) session.commit() else: User.get_or_create(session, username=username) session.commit() mock_load_plugins.return_value = [FakeOneOff()] # dry_run call_main(session, tmpdir, "oneoff", "run", "FakeOneOff") assert User.get(session, name=username) is None, "default dry_run means no writes" assert User.get(session, name=other_username) is None, '"valuewith= not in arg' assert Group.get( session, name=groupname) is None, '"group" not in arg so no group created' # not dry_run, create a user call_main(session, tmpdir, "oneoff", "run", "--no-dry_run", "FakeOneOff") assert User.get(session, name=username) is not None, "dry_run off means writes" assert User.get(session, name=other_username) is None, '"valuewith= not in arg' assert Group.get( session, name=groupname) is None, '"group" not in arg so no group created' # not dry_run, use kwarg to create a group call_main(session, tmpdir, "oneoff", "run", "--no-dry_run", "FakeOneOff", "group=1") assert User.get(session, name=username) is not None, "dry_run off means writes" assert User.get(session, name=other_username) is None, '"valuewith= not in arg' assert Group.get( session, name=groupname) is not None, '"group" in arg so group created' # invalid format for argument should result in premature system exit with pytest.raises(SystemExit): call_main(session, tmpdir, "oneoff", "run", "--no-dry_run", "FakeOneOff", "bad_arg") call_main(session, tmpdir, "oneoff", "run", "--no-dry_run", "FakeOneOff", "key=valuewith=") assert User.get( session, name=other_username) is not None, '"valuewith= in arg, create user2'
def notify_edge_expiration(settings, session, edge): """Send notification that an edge has expired. Handles email notification and audit logging. Args: settings (Settings): Grouper Settings object for current run. session (Session): Object for db session. edge (GroupEdge): The expiring edge. """ # TODO(herb): get around circular depdendencies; long term remove call to # send_async_email() from grouper.models from grouper.models.group import Group # TODO(rra): Arbitrarily use the first listed owner of the group from which membership expired # as the actor, since we have to provide an actor and we didn't record who set the expiration on # the edge originally. actor_id = next(edge.group.my_owners().itervalues()).id # Pull data about the edge and the affected user or group. group_name = edge.group.name if OBJ_TYPES_IDX[edge.member_type] == "User": user = User.get(session, pk=edge.member_pk) member_name = user.username recipients = [member_name] member_is_user = True else: subgroup = Group.get(session, pk=edge.member_pk) member_name = subgroup.groupname recipients = subgroup.my_owners_as_strings() member_is_user = False # Log to the audit log. How depends on whether a user's membership has expired or a group's # membership has expired. audit_data = { "action": "expired_from_group", "actor_id": actor_id, "description": "{} expired out of the group".format(member_name), } if member_is_user: AuditLog.log(session, on_user_id=user.id, on_group_id=edge.group_id, **audit_data) else: # Make an audit log entry for both the subgroup and the parent group so that it will show up # in the FE view for both groups. AuditLog.log(session, on_group_id=edge.group_id, **audit_data) AuditLog.log(session, on_group_id=subgroup.id, **audit_data) # Send email notification to the affected people. email_context = { "group_name": group_name, "member_name": member_name, "member_is_user": member_is_user, } send_email( session=session, recipients=recipients, subject="Membership in {} expired".format(group_name), template="expiration", settings=settings, context=email_context, )
def create_request(session: Session, user: User, group: Group, permission: Permission, argument: str, reason: str) -> PermissionRequest: """Creates an permission request and sends notification to the responsible approvers. Args: session: Database session user: User requesting permission group: Group requested permission would be applied to permission: Permission in question to request argument: argument for the given permission reason: reason the permission should be granted Raises: RequestAlreadyExists: Trying to create a request that is already pending NoOwnersAvailable: No owner is available for the requested perm + arg. grouper.audit.UserNotAuditor: The group has owners that are not auditors """ # check if group already has perm + arg pair for _, existing_perm_name, _, existing_perm_argument, _ in group.my_permissions( ): if permission.name == existing_perm_name and argument == existing_perm_argument: raise RequestAlreadyGranted() # check if request already pending for this perm + arg pair existing_count = (session.query(PermissionRequest).filter( PermissionRequest.group_id == group.id, PermissionRequest.permission_id == permission.id, PermissionRequest.argument == argument, PermissionRequest.status == "pending", ).count()) if existing_count > 0: raise RequestAlreadyExists() # determine owner(s) owners_by_arg_by_perm = get_owners_by_grantable_permission( session, separate_global=True) owner_arg_list = get_owner_arg_list( session, permission, argument, owners_by_arg_by_perm=owners_by_arg_by_perm) if not owner_arg_list: raise NoOwnersAvailable() if permission.audited: # will raise UserNotAuditor if any owner of the group is not an auditor assert_controllers_are_auditors(group) pending_status = "pending" now = datetime.utcnow() # multiple steps to create the request request = PermissionRequest( requester_id=user.id, group_id=group.id, permission_id=permission.id, argument=argument, status=pending_status, requested_at=now, ).add(session) session.flush() request_status_change = PermissionRequestStatusChange( request=request, user=user, to_status=pending_status, change_at=now).add(session) session.flush() Comment( obj_type=OBJ_TYPES_IDX.index("PermissionRequestStatusChange"), obj_pk=request_status_change.id, user_id=user.id, comment=reason, created_on=now, ).add(session) # send notification email_context = { "user_name": user.name, "group_name": group.name, "permission_name": permission.name, "argument": argument, "reason": reason, "request_id": request.id, "references_header": request.reference_id, } # TODO: would be nicer if it told you which group you're an approver of # that's causing this notification mail_to = [] global_owners = owners_by_arg_by_perm[GLOBAL_OWNERS]["*"] non_wildcard_owners = [ grant for grant in owner_arg_list if grant[1] != "*" ] non_global_owners = [ grant for grant in owner_arg_list if grant[0] not in global_owners ] if any(non_wildcard_owners): # non-wildcard owners should get all the notifications mailto_owner_arg_list = non_wildcard_owners elif any(non_global_owners): mailto_owner_arg_list = non_global_owners else: # only the wildcards so they get the notifications mailto_owner_arg_list = owner_arg_list for owner, arg in mailto_owner_arg_list: if owner.email_address: mail_to.append(owner.email_address) else: mail_to.extend([u for t, u in owner.my_members() if t == "User"]) template_engine = EmailTemplateEngine(settings()) subject_template = template_engine.get_template( "email/pending_permission_request_subj.tmpl") subject = subject_template.render(permission=permission.name, group=group.name) send_email(session, set(mail_to), subject, "pending_permission_request", settings(), email_context) return request
def test_sa_pubkeys(session, users, http_client, base_url): # noqa: F811 user = users["*****@*****.**"] # Add account create_role_user(session, user, "*****@*****.**", "Hi", "canjoin") u = User.get(session, name="*****@*****.**") g = Group.get(session, name="*****@*****.**") assert u is not None assert g is not None assert is_role_user(session, user=u) assert is_role_user(session, group=g) assert get_role_user(session, user=u).group.id == g.id assert get_role_user(session, group=g).user.id == u.id assert not is_role_user(session, user=user) assert not is_role_user(session, group=Group.get(session, name="team-sre")) assert not get_public_keys_of_user(session, user.id) with pytest.raises(HTTPError): # add it fe_url = url(base_url, "/users/{}/public-key/add".format("*****@*****.**")) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"public_key": SSH_KEY_1}), headers={"X-Grouper-User": "******"}, ) # add it fe_url = url(base_url, "/users/{}/public-key/add".format("*****@*****.**")) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"public_key": SSH_KEY_1}), headers={"X-Grouper-User": user.username}, ) assert resp.code == 200 # add bad key -- shouldn't add fe_url = url(base_url, "/users/{}/public-key/add".format("*****@*****.**")) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"public_key": SSH_KEY_BAD}), headers={"X-Grouper-User": user.username}, ) assert resp.code == 200 sa = User.get(session, name="*****@*****.**") keys = get_public_keys_of_user(session, sa.id) assert len(keys) == 1 assert keys[0].public_key == SSH_KEY_1 with pytest.raises(HTTPError): # delete it fe_url = url( base_url, "/users/{}/public-key/{}/delete".format("*****@*****.**", keys[0].id) ) resp = yield http_client.fetch( fe_url, method="POST", body="", headers={"X-Grouper-User": "******"} ) # delete it fe_url = url( base_url, "/users/{}/public-key/{}/delete".format("*****@*****.**", keys[0].id) ) resp = yield http_client.fetch( fe_url, method="POST", body="", headers={"X-Grouper-User": user.username} ) assert resp.code == 200 sa = User.get(session, name="*****@*****.**") assert not get_public_keys_of_user(session, sa.id)
def create_group(self, name, description, join_policy): # type: (str, str, GroupJoinPolicy) -> None group = SQLGroup(groupname=name, description=description, canjoin=join_policy.value) group.add(self.session)