def test_remove_last_owner_via_audit(async_server, browser, users, groups, session): # noqa: F811 future = datetime.utcnow() + timedelta(1) add_member(groups["auditors"], users["*****@*****.**"], role="owner") add_member(groups["audited-team"], users["*****@*****.**"], role="owner", expiration=future) session.commit() fe_url = url(async_server, "/audits/create") browser.get(fe_url) page = AuditsCreatePage(browser) page.set_end_date(future.strftime("%m/%d/%Y")) page.submit() fe_url = url(async_server, "/groups/audited-team") browser.get(fe_url) page = GroupViewPage(browser) audit_modal = page.get_audit_modal() audit_modal.find_member_row("*****@*****.**").set_audit_status("remove") audit_modal.confirm() assert page.current_url.endswith("/groups/audited-team") assert page.has_text(group_ownership_policy.EXCEPTION_MESSAGE)
def test_can_add_owner_twice(get_plugin_proxy, session, groups, users): # noqa: F811 get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) group = groups["team-infra"] owner = users["*****@*****.**"] add_member(group, owner, role="owner") add_member(group, owner, role="owner")
def test_can_disable_member(get_plugin_proxy, session, groups, users): # noqa: F811 get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) group = groups["team-infra"] member = users["*****@*****.**"] add_member(group, member) disable_user(session, member)
def test_cant_demote_last_owner(get_plugin_proxy, groups, users): # noqa: F811 get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) group = groups["team-infra"] owner = users["*****@*****.**"] add_member(group, owner, role="owner") with pytest.raises(PluginRejectedGroupMembershipUpdate): group.edit_member(owner, owner, "Unit Testing", role="member")
def test_cant_disable_last_owner(get_plugin_proxy, session, groups, users): # noqa: F811 get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) group = groups["team-infra"] owner = users["*****@*****.**"] add_member(group, owner, role="owner") with pytest.raises(PluginRejectedDisablingUser): disable_user(session, owner)
def test_cant_expire_last_owner(get_plugin_proxy, groups, users): # noqa: F811 get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) group = groups["team-infra"] owner = users["*****@*****.**"] expiration = datetime.utcnow() + timedelta(1) add_member(group, owner, role="owner") with pytest.raises(PluginRejectedGroupMembershipUpdate): group.edit_member(owner, owner, "Unit Testing", expiration=expiration)
def test_can_always_revoke_members(get_plugin_proxy, groups, users): # noqa: F811 get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) group = groups["team-infra"] owner = users["*****@*****.**"] member = users["*****@*****.**"] expiration = datetime.utcnow() + timedelta(1) add_member(group, owner, role="owner", expiration=expiration) add_member(group, member) revoke_member(group, member)
def test_cant_revoke_last_permanent_owner(get_plugin_proxy, groups, users): # noqa: F811 get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) group = groups["team-infra"] first_owner = users["*****@*****.**"] second_owner = users["*****@*****.**"] expiration = datetime.utcnow() + timedelta(1) add_member(group, first_owner, role="owner", expiration=expiration) add_member(group, second_owner, role="owner") with pytest.raises(PluginRejectedGroupMembershipUpdate): revoke_member(group, second_owner)
def test_graph_edit_role( session, graph, standard_graph, groups, users, http_client, base_url # noqa: F811 ): """Test that membership role changes are refected in the graph.""" user_role = graph.get_group_details( "tech-ops")["users"]["*****@*****.**"]["rolename"] assert user_role == "np-owner" # Ensure they are auditors so that they can be owner. add_member(groups["auditors"], users["*****@*****.**"]) session.commit() # np-owner cannot upgrade themselves to owner resp = yield http_client.fetch( url(base_url, "/groups/tech-ops/edit/user/[email protected]"), method="POST", headers={"X-Grouper-User": "******"}, body=urlencode({ "role": "owner", "reason": "testing" }), ) assert resp.code == 200 graph.update_from_db(session) user_role = graph.get_group_details( "tech-ops")["users"]["*****@*****.**"]["rolename"] assert user_role == "np-owner" # but an owner can resp = yield http_client.fetch( url(base_url, "/groups/tech-ops/edit/user/[email protected]"), method="POST", headers={"X-Grouper-User": "******"}, body=urlencode({ "role": "owner", "reason": "testing" }), ) assert resp.code == 200 graph.update_from_db(session) user_role = graph.get_group_details( "tech-ops")["users"]["*****@*****.**"]["rolename"] assert user_role == "owner"
def expired_graph(session, graph, groups, users): # noqa: F811 now = datetime.utcnow() # expired user membership add_member(groups["team-sre"], users["*****@*****.**"], role="owner") add_member(groups["team-sre"], users["*****@*****.**"], expiration=now) # expired group membership add_member(groups["serving-team"], users["*****@*****.**"], role="owner") add_member(groups["serving-team"], groups["team-sre"], expiration=now) # expired user membership in disabled group add_member(groups["sad-team"], users["*****@*****.**"], expiration=now) groups["sad-team"].disable() session.commit() return graph
def test_cant_revoke_last_npowner(get_plugin_proxy, session, groups, users): # noqa: F811 get_plugin_proxy.return_value = PluginProxy([GroupOwnershipPolicyPlugin()]) group = groups["team-infra"] first_owner = users["*****@*****.**"] second_owner = users["*****@*****.**"] add_member(group, first_owner, role="np-owner") add_member(group, second_owner, role="np-owner") # Revoking the first owner does not raise an exception revoke_member(group, first_owner) session.commit() with pytest.raises(PluginRejectedGroupMembershipUpdate): revoke_member(group, second_owner)
def test_list_public_keys(async_server, browser, session, users, groups): # noqa: F811 permission = Permission.get_or_create(session, name=AUDIT_SECURITY, description="")[0] user = users["*****@*****.**"] group = groups["group-admins"] add_member(group, user, role="owner") grant_permission(group, permission, "public_keys") # Pagination defaults to 100 keys per page for i in range(120): key = PublicKey( user=user, public_key="KEY:{}".format(i), fingerprint="MD5:{}".format(i), fingerprint_sha256="SHA256:{}".format(i), key_size=4096, key_type="ssh-rsa", comment="", ) key.add(session) session.commit() fe_url = url(async_server, "/users/public-keys") browser.get(fe_url) page = PublicKeysPage(browser) row = page.find_public_key_row("SHA256:0") assert row.user == user.username assert row.key_size == "4096" assert row.key_type == "ssh-rsa" assert page.find_public_key_row("SHA256:99") with pytest.raises(NoSuchElementException): page.find_public_key_row("SHA256:100")
def test_graph_cycle_direct(session, graph, users, groups): # noqa: F811 """ Test adding members where all descendants already exist.""" add_member(groups["team-sre"], users["*****@*****.**"]) add_member(groups["tech-ops"], users["*****@*****.**"]) add_member(groups["team-sre"], groups["tech-ops"]) add_member(groups["tech-ops"], groups["team-sre"]) session.commit() graph.update_from_db(session) assert get_users(graph, "team-sre") == set(["*****@*****.**", "*****@*****.**"]) assert get_users(graph, "team-sre", cutoff=1) == set(["*****@*****.**"]) assert get_users(graph, "tech-ops") == set(["*****@*****.**", "*****@*****.**"]) assert get_users(graph, "tech-ops", cutoff=1) == set(["*****@*****.**"]) assert get_groups(graph, "*****@*****.**") == set(["team-sre", "tech-ops"]) assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["team-sre"]) assert get_groups(graph, "*****@*****.**") == set(["team-sre", "tech-ops"]) assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["tech-ops"])
def standard_graph(session, graph, users, groups, service_accounts, permissions): """Setup a standard graph used for many tests. In graph form: +-----------------------+ | | | team-sre | | * gary (o) +---------------------------------+ | * zay | | | * zorkian | | | * service (s) | +-----------v-----------+ | | | | +-----------------------+ | serving-team | +-----------------------+ +---------> * zorkian (o) | | | | | | | tech-ops | | +-----------+-----------+ | * zay (o) | | | | * gary +-----------+ | | * figurehead (np) | | | | | +-----------------------+ | +-----------------------+ +-----------v-----------+ | | | | | security-team | | team-infra | | * oliver (o) +---------------------> * gary (o) | | * figurehead | | | | | +-----------+-----------+ +-----------------------+ | +-----------------------+ | | | | | sad-team | | | * zorkian (o) | | | * oliver | +-----------v-----------+ | | | | +-----------------------+ | all-teams | +-----------------------+ | * testuser (o) | | | | | | audited-team | +-----------------------+ | * zorkian (o) | | | +-----------------------+ +-----------------------+ | | | auditors | | * zorkian (o) | | | +-----------------------+ +-----------------------+ | | | user-admins | | * tyleromeara (o) | | * cbguder (o) | | | +-----------------------+ +-----------------------+ | | | group-admins | | * cbguder (o) | | | +-----------------------+ +-----------------------+ | | | permission-admins | | * gary (o) | | * cbguder | | | +-----------------------+ Arrows denote member of the source in the destination group. (o) for owners, (np) for non-permissioned owners, (s) for service accounts. """ add_member(groups["team-sre"], users["*****@*****.**"], role="owner") add_member(groups["team-sre"], users["*****@*****.**"]) add_member(groups["team-sre"], users["*****@*****.**"]) grant_permission(groups["team-sre"], permissions["ssh"], argument="*") grant_permission(groups["team-sre"], permissions["team-sre"], argument="*") add_member(groups["serving-team"], users["*****@*****.**"], role="owner") add_member(groups["serving-team"], groups["team-sre"]) add_member(groups["serving-team"], groups["tech-ops"]) grant_permission(groups["serving-team"], permissions["audited"]) add_member(groups["tech-ops"], users["*****@*****.**"], role="owner") add_member(groups["tech-ops"], users["*****@*****.**"]) add_member(groups["tech-ops"], users["*****@*****.**"], role="np-owner") grant_permission(groups["tech-ops"], permissions["ssh"], argument="shell") add_member(groups["security-team"], users["*****@*****.**"], role="owner") add_member(groups["security-team"], users["*****@*****.**"], role="member") add_member(groups["sad-team"], users["*****@*****.**"], role="owner") add_member(groups["sad-team"], users["*****@*****.**"]) grant_permission(groups["sad-team"], permissions["owner"], argument="sad-team") add_member(groups["audited-team"], users["*****@*****.**"], role="owner") grant_permission(groups["audited-team"], permissions["audited"]) add_member(groups["team-infra"], users["*****@*****.**"], role="owner") add_member(groups["team-infra"], groups["serving-team"]) add_member(groups["team-infra"], groups["security-team"]) grant_permission(groups["team-infra"], permissions["sudo"], argument="shell") add_member(groups["auditors"], users["*****@*****.**"], role="owner") grant_permission(groups["auditors"], permissions[AUDIT_VIEWER]) grant_permission(groups["auditors"], permissions[AUDIT_MANAGER]) grant_permission(groups["auditors"], permissions[PERMISSION_AUDITOR]) add_member(groups["all-teams"], users["*****@*****.**"], role="owner") add_member(groups["all-teams"], groups["team-infra"]) add_member(groups["user-admins"], users["*****@*****.**"], role="owner") add_member(groups["user-admins"], users["*****@*****.**"], role="owner") grant_permission(groups["user-admins"], permissions[USER_ADMIN]) add_member(groups["group-admins"], users["*****@*****.**"], role="owner") grant_permission(groups["group-admins"], permissions[GROUP_ADMIN]) add_member(groups["permission-admins"], users["*****@*****.**"], role="owner") add_member(groups["permission-admins"], users["*****@*****.**"], role="member") grant_permission(groups["permission-admins"], permissions[PERMISSION_ADMIN]) session.commit() graph.update_from_db(session) return graph
def expiring_graph(session, graph, users, groups, permissions): # noqa: F811 now = datetime.utcnow() note_exp_now = now + timedelta(settings().expiration_notice_days) week = timedelta(7) add_member(groups["team-sre"], users["*****@*****.**"], role="owner") add_member(groups["team-sre"], users["*****@*****.**"], role="owner") add_member(groups["team-sre"], users["*****@*****.**"], expiration=note_exp_now + week) add_member(groups["team-sre"], users["*****@*****.**"]) add_member( groups["team-sre"], users["*****@*****.**"], role="owner", expiration=note_exp_now + week ) revoke_member(groups["team-sre"], users["*****@*****.**"]) grant_permission(groups["team-sre"], permissions["ssh"], argument="*") add_member(groups["serving-team"], users["*****@*****.**"], role="owner") add_member(groups["serving-team"], groups["team-sre"], expiration=note_exp_now + week) add_member(groups["serving-team"], groups["tech-ops"]) grant_permission(groups["serving-team"], permissions["audited"]) add_member(groups["tech-ops"], users["*****@*****.**"], role="owner") add_member(groups["tech-ops"], users["*****@*****.**"], expiration=note_exp_now + 2 * week) grant_permission(groups["tech-ops"], permissions["ssh"], argument="shell") return graph
def test_audit_end_to_end(session, users, groups, http_client, base_url, graph): # noqa: F811 """ Tests an end-to-end audit cycle. """ groupname = "audited-team" gary_id = users["*****@*****.**"].id # make everyone an auditor or global audit will have issues add_member(groups["auditors"], users["*****@*****.**"]) add_member(groups["auditors"], users["*****@*****.**"]) add_member(groups["auditors"], users["*****@*****.**"]) add_member(groups["auditors"], users["*****@*****.**"]) # add some users to test removal add_member(groups[groupname], users["*****@*****.**"]) add_member(groups[groupname], users["*****@*****.**"]) graph.update_from_db(session) # start the audit end_at_str = (datetime.now() + timedelta(days=10)).strftime("%m/%d/%Y") fe_url = url(base_url, "/audits/create") resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"ends_at": end_at_str}), headers={"X-Grouper-User": "******"}, ) assert resp.code == 200 open_audits = get_audits(session, only_open=True).all() assert len(open_audits) == 4, "audits created" assert groupname in [x.group.name for x in open_audits ], "group we expect also gets audit" # pull all the info we need to resolve audits, avoids detached sqlalchemy sessions # (DetachedInstanceError) all_group_ids = [x.group.id for x in open_audits] open_audits = [ Audit( x.id, next(iter(x.group.my_owners())), x.group.name, [ MyAuditMemberInfo( ami.audit_member_obj.id, ami.audit_member_obj.edge.member_type, ami.audit_member_obj.edge_id, ) for ami in get_group_audit_members_infos(session, x.group) ], ) for x in open_audits ] # approve everything but the one we added members to for one_audit in open_audits: fe_url = url(base_url, "/audits/{}/complete".format(one_audit.audit_id)) if one_audit.group_name == groupname: continue # blanket approval body = urlencode({ "audit_{}".format(ami.am_id): "approved" for ami in one_audit.audit_members_infos }) resp = yield http_client.fetch( fe_url, method="POST", body=body, headers={"X-Grouper-User": one_audit.owner_name}) assert resp.code == 200 open_audits = get_audits(session, only_open=True).all() assert len(open_audits) == 1, "only our test group remaining" one_audit = open_audits[0] one_audit.id body_dict = {} for ami in get_group_audit_members_infos(session, one_audit.group): if gary_id == ami.member_obj.id: # deny body_dict["audit_{}".format(ami.audit_member_obj.id)] = "remove" else: # approve body_dict["audit_{}".format(ami.audit_member_obj.id)] = "approved" owner_name = next(iter(one_audit.group.my_owners())) fe_url = url(base_url, "/audits/{}/complete".format(one_audit.id)) resp = yield http_client.fetch(fe_url, method="POST", body=urlencode(body_dict), headers={"X-Grouper-User": owner_name}) assert resp.code == 200 # check all the logs assert len(AuditLog.get_entries( session, action="start_audit")) == 1, "global start is logged" assert (len(AuditLog.get_entries( session, action="complete_global_audit")) == 1), "global complete is logged" for group_id in all_group_ids: assert (len( AuditLog.get_entries( session, on_group_id=group_id, action="complete_audit", category=AuditLogCategory.audit, )) == 1), "complete entry for each group" assert (len( AuditLog.get_entries(session, on_user_id=gary_id, category=AuditLogCategory.audit)) == 1 ), "removal AuditLog entry on user"
def test_graph_add_member_existing(session, graph, users, groups): # noqa: F811 """ Test adding members to an existing relationship.""" add_member(groups["team-sre"], users["*****@*****.**"], role="owner") add_member(groups["tech-ops"], users["*****@*****.**"], role="owner") add_member(groups["team-infra"], users["*****@*****.**"], role="owner") add_member(groups["team-infra"], groups["team-sre"]) add_member(groups["team-infra"], groups["tech-ops"]) add_member(groups["all-teams"], users["*****@*****.**"], role="owner") add_member(groups["all-teams"], groups["team-infra"]) add_member(groups["team-sre"], users["*****@*****.**"]) add_member(groups["tech-ops"], users["*****@*****.**"]) session.commit() graph.update_from_db(session) assert get_users(graph, "team-sre") == set(["*****@*****.**", "*****@*****.**"]) assert get_users(graph, "tech-ops") == set(["*****@*****.**", "*****@*****.**"]) assert get_users(graph, "team-infra") == set(["*****@*****.**", "*****@*****.**"]) assert get_users(graph, "team-infra", cutoff=1) == set(["*****@*****.**"]) assert get_users(graph, "all-teams") == set( ["*****@*****.**", "*****@*****.**", "*****@*****.**"]) assert get_users(graph, "all-teams", cutoff=1) == set(["*****@*****.**"]) assert get_groups(graph, "*****@*****.**") == set( ["team-sre", "all-teams", "tech-ops", "team-infra"]) assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["team-sre", "tech-ops", "team-infra"]) assert get_groups(graph, "*****@*****.**") == set( ["team-sre", "all-teams", "tech-ops", "team-infra"]) assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["team-sre", "tech-ops"]) assert get_groups(graph, "*****@*****.**") == set(["all-teams"]) assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["all-teams"])
def test_graph_cycle_indirect(session, graph, users, groups): # noqa: F811 """ Test adding a member that will create a cycle. gary zay testuser | | | sre <----- tech-ops <----- team-infra <-- | | | | --------> all-teams -------------------- """ add_member(groups["team-sre"], users["*****@*****.**"]) add_member(groups["tech-ops"], users["*****@*****.**"]) add_member(groups["team-infra"], users["*****@*****.**"]) add_member(groups["team-sre"], groups["tech-ops"]) add_member(groups["tech-ops"], groups["team-infra"]) add_member(groups["team-infra"], groups["all-teams"]) add_member(groups["all-teams"], groups["team-sre"]) session.commit() graph.update_from_db(session) all_users = set(["*****@*****.**", "*****@*****.**", "*****@*****.**"]) all_groups = set(["team-sre", "all-teams", "tech-ops", "team-infra"]) assert get_users(graph, "team-sre") == all_users assert get_users(graph, "team-sre", cutoff=1) == set(["*****@*****.**"]) assert get_users(graph, "tech-ops") == all_users assert get_users(graph, "tech-ops", cutoff=1) == set(["*****@*****.**"]) assert get_users(graph, "team-infra") == all_users assert get_users(graph, "team-infra", cutoff=1) == set(["*****@*****.**"]) assert get_users(graph, "all-teams") == all_users assert get_users(graph, "all-teams", cutoff=1) == set([]) assert get_groups(graph, "*****@*****.**") == all_groups assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["team-sre"]) assert get_groups(graph, "*****@*****.**") == all_groups assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["tech-ops"]) assert get_groups(graph, "*****@*****.**") == all_groups assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["team-infra"])
def test_expiration_notifications( expiring_graph, session, users, groups, permissions # noqa: F811 ): now = datetime.utcnow() note_exp_now = now + timedelta(settings().expiration_notice_days) day = timedelta(1) week = timedelta(7) # What expirations are coming up in the next day? Next week? upcoming_expirations = _get_unsent_expirations(session, now + day) assert upcoming_expirations == [] upcoming_expirations = _get_unsent_expirations(session, now + week) assert sorted(upcoming_expirations) == [ # Group, subgroup, subgroup owners. ("serving-team", "team-sre", "*****@*****.**"), ("serving-team", "team-sre", "*****@*****.**"), # Group, user, user. ("team-sre", "*****@*****.**", "*****@*****.**"), ] # Make someone expire a week from now. edit_member(groups["team-sre"], users["*****@*****.**"], expiration=note_exp_now + week) upcoming_expirations = _get_unsent_expirations(session, now + week) assert sorted(upcoming_expirations) == [ # Group, subgroup, subgroup owners. ("serving-team", "team-sre", "*****@*****.**"), ("serving-team", "team-sre", "*****@*****.**"), # Group, user, user. ("team-sre", "*****@*****.**", "*****@*****.**"), ("team-sre", "*****@*****.**", "*****@*****.**"), ] # Now cancel that expiration. edit_member(groups["team-sre"], users["*****@*****.**"], expiration=None) upcoming_expirations = _get_unsent_expirations(session, now + week) assert sorted(upcoming_expirations) == [ # Group, subgroup, subgroup owners. ("serving-team", "team-sre", "*****@*****.**"), ("serving-team", "team-sre", "*****@*****.**"), # Group, user, user. ("team-sre", "*****@*****.**", "*****@*****.**"), ] # Make an ordinary member an owner. edit_member(groups["team-sre"], users["*****@*****.**"], role="owner") upcoming_expirations = _get_unsent_expirations(session, now + week) assert sorted(upcoming_expirations) == [ # Group, subgroup, subgroup owners. ("serving-team", "team-sre", "*****@*****.**"), ("serving-team", "team-sre", "*****@*****.**"), ("serving-team", "team-sre", "*****@*****.**"), # Group, user, user. ("team-sre", "*****@*****.**", "*****@*****.**"), ] # Make an owner an ordinary member. edit_member(groups["team-sre"], users["*****@*****.**"], role="member") upcoming_expirations = _get_unsent_expirations(session, now + week) assert sorted(upcoming_expirations) == [ # Group, subgroup, subgroup owners. ("serving-team", "team-sre", "*****@*****.**"), ("serving-team", "team-sre", "*****@*****.**"), # Group, user, user. ("team-sre", "*****@*****.**", "*****@*****.**"), ] # Send notices about expirations coming up in the next day, next week. notices_sent = process_async_emails(settings(), session, now + day, dry_run=True) assert notices_sent == 0 notices_sent = process_async_emails(settings(), session, now + week, dry_run=True) assert notices_sent == 3 # ("serving-team", "team-sre", "*****@*****.**") # ("serving-team", "team-sre", "*****@*****.**") # ("team-sre", "*****@*****.**", "*****@*****.**") # Notices in the upcoming week have already been sent, but there's another # two weeks from now. upcoming_expirations = _get_unsent_expirations(session, now + week) assert upcoming_expirations == [] upcoming_expirations = _get_unsent_expirations(session, now + 2 * week) assert upcoming_expirations == [("tech-ops", "*****@*****.**", "*****@*****.**")] # We already sent these notices. notices_sent = process_async_emails(settings(), session, now + week, dry_run=True) assert notices_sent == 0 # Extend gary's membership to beyond worth mentioning expiration in two weeks. add_member(groups["tech-ops"], users["*****@*****.**"], expiration=note_exp_now + 3 * week) upcoming_expirations = _get_unsent_expirations(session, now + 2 * week) assert upcoming_expirations == [] notices_sent = process_async_emails(settings(), session, now + 2 * week, dry_run=True) assert notices_sent == 0
def setup_desc_to_ances(session, users, groups): # noqa: F811 add_member(groups["team-sre"], users["*****@*****.**"], role="owner") add_member(groups["team-sre"], users["*****@*****.**"]) add_member(groups["tech-ops"], users["*****@*****.**"], role="owner") add_member(groups["tech-ops"], users["*****@*****.**"]) add_member(groups["team-infra"], users["*****@*****.**"], role="owner") add_member(groups["team-infra"], groups["team-sre"]) add_member(groups["team-infra"], groups["tech-ops"]) add_member(groups["all-teams"], users["*****@*****.**"], role="owner") add_member(groups["all-teams"], groups["team-infra"])
def test_aggregate_request( graph, groups, permissions, session, standard_graph, users # noqa: F811 ): gary = users["*****@*****.**"] not_involved = [ user for name, user in iteritems(users) if name not in ("*****@*****.**", "*****@*****.**") ] assert not any( [user_requests_aggregate(session, u).all() for u in itervalues(users)] ), "should have no pending requests to begin with" # one request to one team groups["team-sre"].add_member( users["*****@*****.**"], users["*****@*****.**"], reason="for the lulz" ) session.commit() assert len(user_requests_aggregate(session, gary).all()) == 1, "one pending request for owner" assert not any( [user_requests_aggregate(session, u).all() for u in not_involved] ), "no pending requests if you're not the owner" # two request to two teams, same owner groups["team-infra"].add_member( users["*****@*****.**"], users["*****@*****.**"], reason="for the lulz" ) session.commit() request_gary = user_requests_aggregate(session, gary).all() assert len(request_gary) == 2, "two pending request for owner" assert not any( [user_requests_aggregate(session, u).all() for u in not_involved] ), "no pending requests if you're not the owner" # resolving one request should reflect request = session.query(Request).filter_by(id=request_gary[0].id).scalar() request.update_status(users["*****@*****.**"], "actioned", "for being a good person") session.commit() assert len(user_requests_aggregate(session, gary).all()) == 1, "one pending request for owner" assert not any( [user_requests_aggregate(session, u).all() for u in not_involved] ), "no pending requests if you're not the owner" # requests to dependent teams should reflect apprpriately groups["security-team"].add_member( users["*****@*****.**"], users["*****@*****.**"], reason="for the lulz" ) session.commit() assert ( len(user_requests_aggregate(session, gary).all()) == 1 ), "super owner should not get request" assert ( len(user_requests_aggregate(session, users["*****@*****.**"]).all()) == 1 ), "owner should get request" user_not_gary_oliver = [ u for n, u in iteritems(users) if n not in ("*****@*****.**", "*****@*****.**") ] assert not any([user_requests_aggregate(session, u).all() for u in user_not_gary_oliver]) # manager and np-owner should get requests figurehead = users["*****@*****.**"] add_member(groups["audited-team"], figurehead, role="manager") assert ( len(user_requests_aggregate(session, figurehead).all()) == 0 ), "no request for np-owner at first" groups["tech-ops"].add_member( users["*****@*****.**"], users["*****@*****.**"], reason="for the lulz" ) assert len(user_requests_aggregate(session, figurehead).all()) == 1, "request for np-owner" groups["audited-team"].add_member( users["*****@*****.**"], users["*****@*****.**"], reason="for the lulz" ) assert ( len(user_requests_aggregate(session, figurehead).all()) == 2 ), "request for np-owner and manager"
def test_promote_nonauditors( mock_gagn, standard_graph, graph, users, groups, session, permissions # noqa: F811 ): """ Test expiration auditing and notification. """ assert graph.get_group_details("audited-team")["audited"] # # Ensure auditors promotion for all approvers # approver_roles = ["owner", "np-owner", "manager"] affected_users = set( ["*****@*****.**", "*****@*****.**", "*****@*****.**", "*****@*****.**"]) for idx, role in enumerate(approver_roles): # Add non-auditor as an approver to an audited group add_member(groups["audited-team"], users["*****@*****.**"], role=role) graph.update_from_db(session) assert not affected_users.intersection(get_users(graph, "auditors")) # do the promotion logic background = BackgroundProcessor(settings, None) background.promote_nonauditors(session) # Check that the users now added to auditors group graph.update_from_db(session) assert affected_users.intersection(get_users( graph, "auditors")) == affected_users unsent_emails = _get_unsent_emails_and_send(session) assert any([ 'Subject: Added as member to group "auditors"' in email.body and "To: [email protected]" in email.body for email in unsent_emails ]) assert any([ 'Subject: Added as member to group "auditors"' in email.body and "To: [email protected]" in email.body for email in unsent_emails ]) assert any([ 'Subject: Added as member to group "auditors"' in email.body and "To: [email protected]" in email.body for email in unsent_emails ]) audits = AuditLog.get_entries(session, action="nonauditor_promoted") assert len(audits) == len(affected_users) * (idx + 1) # reset for next iteration revoke_member(groups["audited-team"], users["*****@*****.**"]) for username in affected_users: revoke_member(groups["auditors"], users[username]) # # Ensure nonauditor, nonapprovers in audited groups do not get promoted # # first, run a promotion to get any other promotion that we don't # care about out of the way background = BackgroundProcessor(settings, None) background.promote_nonauditors(session) prev_audit_log_count = len( AuditLog.get_entries(session, action="nonauditor_promoted")) member_roles = ["member"] for idx, role in enumerate(member_roles): # Add non-auditor as a non-approver to an audited group add_member(groups["audited-team"], users["*****@*****.**"], role=role) # do the promotion logic background = BackgroundProcessor(settings, None) background.promote_nonauditors(session) # Check that the user is not added to auditors group graph.update_from_db(session) assert "*****@*****.**" not in get_users(graph, "auditors") assert not any([ 'Subject: Added as member to group "auditors"' in email.body and "To: [email protected]" in email.body for email in _get_unsent_emails_and_send(session) ]) audits = AuditLog.get_entries(session, action="nonauditor_promoted") assert len(audits) == prev_audit_log_count revoke_member(groups["audited-team"], users["*****@*****.**"])
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 test_graph_add_member_existing(session, graph, users, groups): # noqa: F811 """ Test adding members to an existing relationship.""" add_member(groups["team-sre"], users["*****@*****.**"], role="owner") add_member(groups["tech-ops"], users["*****@*****.**"], role="owner") add_member(groups["team-infra"], users["*****@*****.**"], role="owner") add_member(groups["team-infra"], groups["team-sre"]) add_member(groups["team-infra"], groups["tech-ops"]) add_member(groups["all-teams"], users["*****@*****.**"], role="owner") add_member(groups["all-teams"], groups["team-infra"]) add_member(groups["team-sre"], users["*****@*****.**"]) add_member(groups["tech-ops"], users["*****@*****.**"]) session.commit() graph.update_from_db(session) assert get_users(graph, "team-sre") == set(["*****@*****.**", "*****@*****.**"]) assert get_users(graph, "tech-ops") == set(["*****@*****.**", "*****@*****.**"]) assert get_users(graph, "team-infra") == set(["*****@*****.**", "*****@*****.**"]) assert get_users(graph, "team-infra", cutoff=1) == set(["*****@*****.**"]) assert get_users(graph, "all-teams") == set(["*****@*****.**", "*****@*****.**", "*****@*****.**"]) assert get_users(graph, "all-teams", cutoff=1) == set(["*****@*****.**"]) assert get_groups(graph, "*****@*****.**") == set( ["team-sre", "all-teams", "tech-ops", "team-infra"] ) assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["team-sre", "tech-ops", "team-infra"]) assert get_groups(graph, "*****@*****.**") == set( ["team-sre", "all-teams", "tech-ops", "team-infra"] ) assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["team-sre", "tech-ops"]) assert get_groups(graph, "*****@*****.**") == set(["all-teams"]) assert get_groups(graph, "*****@*****.**", cutoff=1) == set(["all-teams"])
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 test_audit_end_to_end(session, users, groups, http_client, base_url, graph): # noqa: F811 """ Tests an end-to-end audit cycle. """ groupname = "audited-team" gary_id = users["*****@*****.**"].id # make everyone an auditor or global audit will have issues add_member(groups["auditors"], users["*****@*****.**"]) add_member(groups["auditors"], users["*****@*****.**"]) add_member(groups["auditors"], users["*****@*****.**"]) add_member(groups["auditors"], users["*****@*****.**"]) # add some users to test removal add_member(groups[groupname], users["*****@*****.**"]) add_member(groups[groupname], users["*****@*****.**"]) graph.update_from_db(session) # start the audit end_at_str = (datetime.now() + timedelta(days=10)).strftime("%m/%d/%Y") fe_url = url(base_url, "/audits/create") resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"ends_at": end_at_str}), headers={"X-Grouper-User": "******"}, ) assert resp.code == 200 open_audits = get_audits(session, only_open=True).all() assert len(open_audits) == 4, "audits created" assert groupname in [x.group.name for x in open_audits], "group we expect also gets audit" # pull all the info we need to resolve audits, avoids detached sqlalchemy sessions AuditMember = namedtuple("AuditMember", "am_id, edge_type, edge_id") Audit = namedtuple("Audit", "audit_id, owner_name, group_name, audit_members") all_group_ids = [x.group.id for x in open_audits] open_audits = [ Audit( x.id, next(iter(x.group.my_owners())), x.group.name, [AuditMember(am.id, am.edge.member_type, am.edge_id) for am in x.my_members()], ) for x in open_audits ] # approve everything but the one we added members to for one_audit in open_audits: fe_url = url(base_url, "/audits/{}/complete".format(one_audit.audit_id)) if one_audit.group_name == groupname: continue # blanket approval body = urlencode( {"audit_{}".format(am.am_id): "approved" for am in one_audit.audit_members} ) resp = yield http_client.fetch( fe_url, method="POST", body=body, headers={"X-Grouper-User": one_audit.owner_name} ) assert resp.code == 200 open_audits = get_audits(session, only_open=True).all() assert len(open_audits) == 1, "only our test group remaining" one_audit = open_audits[0] one_audit.id body_dict = {} for am in one_audit.my_members(): if gary_id == am.member.id: # deny body_dict["audit_{}".format(am.id)] = "remove" else: # approve body_dict["audit_{}".format(am.id)] = "approved" owner_name = next(iter(one_audit.group.my_owners())) fe_url = url(base_url, "/audits/{}/complete".format(one_audit.id)) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode(body_dict), headers={"X-Grouper-User": owner_name} ) assert resp.code == 200 # check all the logs assert len(AuditLog.get_entries(session, action="start_audit")) == 1, "global start is logged" assert ( len(AuditLog.get_entries(session, action="complete_global_audit")) == 1 ), "global complete is logged" for group_id in all_group_ids: assert ( len( AuditLog.get_entries( session, on_group_id=group_id, action="complete_audit", category=AuditLogCategory.audit, ) ) == 1 ), "complete entry for each group" assert ( len(AuditLog.get_entries(session, on_user_id=gary_id, category=AuditLogCategory.audit)) == 1 ), "removal AuditLog entry on user"
def test_aggregate_request( graph, groups, permissions, session, standard_graph, users # noqa: F811 ): gary = users["*****@*****.**"] not_involved = [ user for name, user in users.items() if name not in ("*****@*****.**", "*****@*****.**") ] assert not any([ user_requests_aggregate(session, u).all() for u in users.values() ]), "should have no pending requests to begin with" # one request to one team groups["team-sre"].add_member(users["*****@*****.**"], users["*****@*****.**"], reason="for the lulz") session.commit() assert len(user_requests_aggregate( session, gary).all()) == 1, "one pending request for owner" assert not any( [user_requests_aggregate(session, u).all() for u in not_involved]), "no pending requests if you're not the owner" # two request to two teams, same owner groups["team-infra"].add_member(users["*****@*****.**"], users["*****@*****.**"], reason="for the lulz") session.commit() request_gary = user_requests_aggregate(session, gary).all() assert len(request_gary) == 2, "two pending request for owner" assert not any( [user_requests_aggregate(session, u).all() for u in not_involved]), "no pending requests if you're not the owner" # resolving one request should reflect request = session.query(Request).filter_by(id=request_gary[0].id).scalar() request.update_status(users["*****@*****.**"], "actioned", "for being a good person") session.commit() assert len(user_requests_aggregate( session, gary).all()) == 1, "one pending request for owner" assert not any( [user_requests_aggregate(session, u).all() for u in not_involved]), "no pending requests if you're not the owner" # requests to dependent teams should reflect apprpriately groups["security-team"].add_member(users["*****@*****.**"], users["*****@*****.**"], reason="for the lulz") session.commit() assert (len(user_requests_aggregate( session, gary).all()) == 1), "super owner should not get request" assert (len(user_requests_aggregate( session, users["*****@*****.**"]).all()) == 1), "owner should get request" user_not_gary_oliver = [ u for n, u in users.items() if n not in ("*****@*****.**", "*****@*****.**") ] assert not any([ user_requests_aggregate(session, u).all() for u in user_not_gary_oliver ]) # manager and np-owner should get requests figurehead = users["*****@*****.**"] add_member(groups["audited-team"], figurehead, role="manager") assert (len(user_requests_aggregate( session, figurehead).all()) == 0), "no request for np-owner at first" groups["tech-ops"].add_member(users["*****@*****.**"], users["*****@*****.**"], reason="for the lulz") assert len(user_requests_aggregate( session, figurehead).all()) == 1, "request for np-owner" groups["audited-team"].add_member(users["*****@*****.**"], users["*****@*****.**"], reason="for the lulz") assert (len(user_requests_aggregate( session, figurehead).all()) == 2), "request for np-owner and manager"