def test_oneoff(mock_make_session, mock_annex, 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_annex.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( make_session, get_plugin_proxy, session, users, groups # noqa: F811 ): make_session.return_value = session 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, "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, "group", "remove_member", groupname, username) assert (u"User", username) in Group.get(session, name=groupname).my_members()
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_sync_db_default_group( mock_get_auditors_group_name, make_session, session, users, groups # noqa: F811 ): make_session.return_value = session auditors_group = Group.get(session, name="my-auditors") assert not auditors_group, "Auditors group should not exist yet" call_main(session, "sync_db") admin_group = Group.get(session, name="grouper-administrators") assert admin_group, "Group should have been autocreated" admin_group_permission_names = [ perm[1] for perm in admin_group.my_permissions() ] for permission in (GROUP_ADMIN, PERMISSION_ADMIN, USER_ADMIN): assert permission in admin_group_permission_names, ( "Expected permission missing: %s" % permission) auditors_group = Group.get(session, name="my-auditors") assert auditors_group, "Auditors group should have been autocreated" auditors_group_permission_names = [ perm[1] for perm in auditors_group.my_permissions() ] assert PERMISSION_AUDITOR in auditors_group_permission_names, ( "Expected permission missing: %s" % PERMISSION_AUDITOR)
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 post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") group = Group.get(self.session, name=name) if not group: return self.notfound() if not user_can_manage_group(self.session, group, self.current_user): return self.forbidden() form = GroupEditForm(self.request.arguments, obj=group) if not form.validate(): return self.render("group-edit.html", group=group, form=form, alerts=self.get_form_alerts(form.errors)) new_name = form.data["groupname"] renamed = group.groupname != new_name if renamed and is_role_user(self.session, group=group): form.groupname.errors.append( "You cannot change the name of service account groups") return self.render("group-edit.html", group=group, form=form, alerts=self.get_form_alerts(form.errors)) if renamed and Group.get(self.session, name=new_name): message = f"A group named '{new_name}' already exists (possibly disabled)" form.groupname.errors.append(message) return self.render("group-edit.html", group=group, form=form, alerts=self.get_form_alerts(form.errors)) group.groupname = new_name group.email_address = form.data["email_address"] group.description = form.data["description"] group.canjoin = form.data["canjoin"] group.auto_expire = form.data["auto_expire"] group.require_clickthru_tojoin = form.data["require_clickthru_tojoin"] Counter.incr(self.session, "updates") self.session.commit() AuditLog.log(self.session, self.current_user.id, "edit_group", "Edited group.", on_group_id=group.id) url = f"/groups/{group.name}" if renamed: url += "?refresh=yes" self.redirect(url)
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_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_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_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_name_checks(make_session, session, users, groups): # noqa: F811 make_session.return_value = session username = "******" groupname = "team-sre" # check user/group name call_main(session, "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, "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(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 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_create(setup): # type: (SetupTest) -> None with setup.transaction(): setup.add_user_to_group("*****@*****.**", "some-group") setup.add_user_to_group("*****@*****.**", "other-group") run_ctl( setup, "service_account", "--actor", "*****@*****.**", "create", "*****@*****.**", "some-group", "foo +bar -(org)", "this is a service account.\n\n it is for testing", ) service_account = ServiceAccount.get(setup.session, name="*****@*****.**") assert service_account is not None assert service_account.user.name == "*****@*****.**" assert service_account.machine_set == "foo +bar -(org)" assert service_account.description == "this is a service account.\n\n it is for testing" group = Group.get(setup.session, name="some-group") assert group assert get_service_accounts(setup.session, group) == [service_account] # If the account already exists, creating it again returns an error and does nothing. with pytest.raises(SystemExit): run_ctl( setup, "service_account", "--actor", "*****@*****.**", "create", "*****@*****.**", "other-group", "foo", "another test", ) service_account = ServiceAccount.get(setup.session, name="*****@*****.**") assert service_account is not None assert service_account.machine_set == "foo +bar -(org)" assert service_account.description == "this is a service account.\n\n it is for testing" group = Group.get(setup.session, name="some-group") assert group assert get_service_accounts(setup.session, group) == [service_account]
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 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 test_success(setup): # type: (SetupTest) -> None with setup.transaction(): setup.grant_permission_to_group(USER_ADMIN, "", "admins") setup.add_user_to_group("*****@*****.**", "admins") setup.create_user("*****@*****.**") setup.create_group("some-group") mock_ui = MagicMock() usecase = setup.usecase_factory.create_convert_user_to_service_account_usecase( "*****@*****.**", mock_ui) usecase.convert_user_to_service_account("*****@*****.**", "some-group") assert mock_ui.mock_calls == [ call.converted_user_to_service_account("*****@*****.**", "some-group") ] # Check the User after the conversion service_account_user = User.get(setup.session, name="*****@*****.**") assert service_account_user assert service_account_user.is_service_account assert service_account_user.enabled # Check the ServiceAccount that should have been created service_account = ServiceAccount.get(setup.session, name="*****@*****.**") assert service_account assert service_account.description == "" assert service_account.machine_set == "" assert service_account.user_id == service_account_user.id # Check that the ServiceAccount is owned by the correct Group group = Group.get(setup.session, name="some-group") group_service_account = GroupServiceAccount.get( setup.session, service_account_id=service_account.id) assert group assert group_service_account assert group_service_account.group_id == group.id
def post(self, group_id=None, name=None): group = Group.get(self.session, group_id, name) if not group: return self.notfound() if "@" not in self.request.arguments["name"][0]: self.request.arguments["name"][ 0] += "@" + settings.service_account_email_domain if not can_create_service_account(self.session, self.current_user, group): return self.forbidden() form = ServiceAccountCreateForm(self.request.arguments) if not form.validate(): return self.render( "service-account-create.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) if form.data["name"].split( "@")[-1] != settings.service_account_email_domain: form.name.errors.append( "All service accounts must have a username ending in {}". format(settings.service_account_email_domain)) return self.render( "service-account-create.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) try: create_service_account( self.session, self.current_user, form.data["name"], form.data["description"], form.data["machine_set"], group, ) except DuplicateServiceAccount: form.name.errors.append( "A user with name {} already exists".format(form.data["name"])) except BadMachineSet as e: form.machine_set.errors.append(str(e)) if form.name.errors or form.machine_set.errors: return self.render( "service-account-create.html", form=form, group=group, alerts=self.get_form_alerts(form.errors), ) url = "/groups/{}/service/{}?refresh=yes".format( group.name, form.data["name"]) return self.redirect(url)
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 add_group_to_group(self, member, group): # type: (str, str) -> 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, active=True, _role=GROUP_EDGE_ROLES.index("member"), ) edge.add(self.session)
def create_group(self, name): # type: (str) -> None """Create a group, does nothing if it already exists.""" if Group.get(self.session, name=name): return group = Group(groupname=name) group.add(self.session)
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 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 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(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 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 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 or not group.enabled: return self.notfound() group_md = self.graph.get_group_details(group.name) 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() form.member.choices = self._get_choices(group, member_groups, user_is_member) return self.render( "group-join.html", form=form, group=group, audited=group_md["audited"], user_is_member=user_is_member, )
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): 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, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") group = Group.get(self.session, name=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.enable() self.session.commit() AuditLog.log( self.session, self.current_user.id, "enable_group", "Enabled group.", on_group_id=group.id, ) return self.redirect("/groups/{}?refresh=yes".format(group.name))
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 get_auditors_group(settings, session): """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 test_disabling_group_clears_audit(tmpdir: LocalPath, setup: SetupTest, browser: Chrome) -> None: future = datetime.utcnow() + timedelta(days=60) with setup.transaction(): setup.add_user_to_group("*****@*****.**", "some-group", role="owner") setup.add_user_to_group("*****@*****.**", "some-group") setup.create_permission("some-permission", audited=True) setup.grant_permission_to_group("some-permission", "argument", "some-group") setup.add_user_to_group("*****@*****.**", "auditors") setup.grant_permission_to_group(AUDIT_VIEWER, "", "auditors") setup.grant_permission_to_group(AUDIT_MANAGER, "", "auditors") setup.grant_permission_to_group(PERMISSION_AUDITOR, "", "auditors") with frontend_server(tmpdir, "*****@*****.**") as frontend_url: browser.get(url(frontend_url, "/audits/create")) create_page = AuditsCreatePage(browser) create_page.set_end_date(future.strftime("%m/%d/%Y")) create_page.submit() browser.get(url(frontend_url, "/groups/some-group")) group_page = GroupViewPage(browser) assert group_page.subheading == "some-group AUDIT IN PROGRESS" # Check that this created email reminder messages to the group owner. We have to refresh the # session since otherwise SQLite may not see changes. setup.reopen_database() group = Group.get(setup.session, name="some-group") assert group expected_key = f"audit-{group.id}" emails = setup.session.query(AsyncNotification).filter_by( sent=False, email="*****@*****.**").all() assert len(emails) > 0 assert all((e.key is None or e.key == expected_key for e in emails)) assert all(("Group Audit" in e.subject for e in emails)) # Now, disable the group, which should complete the audit. with frontend_server(tmpdir, "*****@*****.**") as frontend_url: browser.get(url(frontend_url, "/groups/some-group")) page = GroupViewPage(browser) audit_modal = page.get_audit_modal() audit_modal.click_close_button() page.wait_until_audit_modal_clears() page.click_disable_button() modal = page.get_disable_modal() modal.confirm() assert page.subheading == "some-group (disabled)" # And now all of the email messages should be marked sent except the immediate one (the one # that wasn't created with async_send_email). setup.reopen_database() emails = setup.session.query(AsyncNotification).filter_by( sent=False, email="*****@*****.**").all() assert len(emails) == 1 assert emails[0].key is None
def test_success(setup): # type: (SetupTest) -> None with setup.transaction(): setup.add_user_to_group("*****@*****.**", "some-group") mock_ui = MagicMock() usecase = setup.usecase_factory.create_create_service_account_usecase("*****@*****.**", mock_ui) usecase.create_service_account( "*****@*****.**", "some-group", "machine-set", "description" ) assert mock_ui.mock_calls == [ call.created_service_account("*****@*****.**", "some-group") ] # Check the User and ServiceAccount that were created. user = User.get(setup.session, name="*****@*****.**") assert user is not None assert user.is_service_account assert user.enabled service = ServiceAccount.get(setup.session, name="*****@*****.**") assert service is not None assert service.machine_set == "machine-set" assert service.description == "description" # Check that the ServiceAccount is owned by the correct Group. group = Group.get(setup.session, name="some-group") assert group is not None linkage = GroupServiceAccount.get(setup.session, service_account_id=service.id) assert linkage is not None assert linkage.group_id == group.id
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 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 user_obj = User.get(session, name=name) assert user_obj, "User object for role user not found" group_obj = Group.get(session, name=name) assert group_obj, "Group object for role user not found" return RoleUser(user_obj, group_obj)
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 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 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 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 = group.my_requests(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 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 get(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") accountname = self.get_path_argument("accountname") self.handle_refresh() group = Group.get(self.session, name=name) if not group: return self.notfound() service_account = ServiceAccount.get(self.session, name=accountname) if not service_account: return self.notfound() # We don't need the group to be valid to find the service account, but ensure that the # group is the owner of the service account so that we don't generate confusing URLs and # broken information on the view page. if service_account.owner.group_id != group.id: return self.notfound() user = service_account.user self.render( "service-account.html", service_account=service_account, group=group, user=user, **get_user_view_template_vars(self.session, self.current_user, user, self.graph) )
def service_account_command(args, settings, session_factory): # type: (Namespace, CtlSettings, SessionFactory) -> None session = session_factory.create_session() actor_user = User.get(session, name=args.actor_name) if not actor_user: logging.fatal('Actor user "{}" is not a valid Grouper user'.format( args.actor_name)) return if args.subcommand == "create": name = args.name if ServiceAccount.get(session, name=name): logging.info("{}: Already exists. Doing nothing.".format(name)) return owner_group = Group.get(session, name=args.owner_group) if not owner_group: logging.fatal('Owner group "{}" does not exist.'.format( args.owner_group)) return logging.info("{}: No such service account, creating...".format(name)) description = args.description machine_set = args.machine_set create_service_account(session, actor_user, name, description, machine_set, owner_group) return
def test_create_as_service_account(setup): """Test that a service account can create another service account.""" with setup.transaction(): setup.create_group("some-group") setup.create_service_account("*****@*****.**", "another-group") setup.grant_permission_to_service_account(USER_ADMIN, "", "*****@*****.**") run_ctl( setup, "service_account", "--actor", "*****@*****.**", "create", "*****@*****.**", "some-group", "foo +bar -(org)", "this is a service account.\n\n it is for testing", ) service_account = ServiceAccount.get(setup.session, name="*****@*****.**") assert service_account is not None assert service_account.user.name == "*****@*****.**" assert service_account.machine_set == "foo +bar -(org)" assert service_account.description == "this is a service account.\n\n it is for testing" group = Group.get(setup.session, name="some-group") assert get_service_accounts(setup.session, group) == [service_account]
def create_service_account(self, name, owner, machine_set, description, initial_metadata=None): # type: (str, str, str, str, Optional[Dict[str,str]]) -> None group = Group.get(self.session, name=owner) if not group: raise GroupNotFoundException(owner) # Create the service account in the database. user = SQLUser(username=name, is_service_account=True) service = SQLServiceAccount(user=user, machine_set=machine_set, description=description) user.add(self.session) service.add(self.session) # Flush the account to allocate an ID. self.session.flush() # Set initial user metadata fields if present. if initial_metadata is not None: for key, value in initial_metadata.items(): # TODO: move this to use the hexagonal architecture model. set_user_metadata(self.session, user.id, key, value) # Create the linkage to the owner. GroupServiceAccount(group_id=group.id, service_account=service).add(self.session)
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_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_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 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_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_grant_and_revoke(session, standard_graph, graph, groups, permissions, http_client, base_url): """Test that permission grant and revokes are reflected correctly.""" group_name = "team-sre" permission_name = "sudo" user_name = "*****@*****.**" def _check_graph_for_perm(graph): return any(map(lambda x: x.permission == permission_name, graph.permission_metadata[group_name])) # make some permission admins perm_admin, _ = Permission.get_or_create(session, name=PERMISSION_ADMIN, description="") session.commit() grant_permission(groups["security-team"], perm_admin) # grant attempt by non-permission admin fe_url = url(base_url, "/permissions/grant/{}".format(group_name)) with pytest.raises(HTTPError): yield http_client.fetch(fe_url, method="POST", body=urlencode({"permission": permission_name, "argument": "specific_arg"}), headers={'X-Grouper-User': "******"}) graph.update_from_db(session) assert not _check_graph_for_perm(graph), "no permissions granted" # grant by permission admin resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({"permission": permission_name, "argument": "specific_arg"}), headers={'X-Grouper-User': user_name}) assert resp.code == 200 graph.update_from_db(session) assert _check_graph_for_perm(graph), "permissions granted, successfully" # figure out mapping_id of grant permission_id = Permission.get(session, name=permission_name).id group_id = Group.get(session, name=group_name).id mapping = session.query(PermissionMap).filter( PermissionMap.permission_id == permission_id, PermissionMap.group_id == group_id).first() # revoke permission by non-admin fe_url = url(base_url, "/permissions/{}/revoke/{}".format(permission_name, mapping.id)) with pytest.raises(HTTPError): yield http_client.fetch(fe_url, method="POST", body=urlencode({}), headers={'X-Grouper-User': "******"}) graph.update_from_db(session) assert _check_graph_for_perm(graph), "permissions not revoked" # revoke permission for realz resp = yield http_client.fetch(fe_url, method="POST", body=urlencode({}), headers={'X-Grouper-User': user_name}) assert resp.code == 200 graph.update_from_db(session) assert not _check_graph_for_perm(graph), "permissions revoked successfully"
def test_group_add_remove_owner(make_session, get_plugin_proxy, session, users, groups): make_session.return_value = session get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) username = '******' groupname = 'team-sre' # add assert (u'User', username) not in groups[groupname].my_members() call_main('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('group', 'remove_member', groupname, username) assert (u'User', username) in Group.get(session, name=groupname).my_members()
def member(self): # TODO(cbguder): get around circular dependencies from grouper.models.group import Group if self.edge.member_type == 0: # User return User.get(self.session, pk=self.edge.member_pk) elif self.edge.member_type == 1: # Group return Group.get(self.session, pk=self.edge.member_pk) raise Exception("invalid member_type in AuditMember!")
def get(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): return self.forbidden() return self.render("group-leave.html", group=group)