def get(self): user = self.get_current_user() if not (user_has_permission(self.session, user, AUDIT_VIEWER) or user_has_permission(self.session, user, AUDIT_MANAGER)): return self.forbidden() offset = int(self.get_argument("offset", 0)) limit = int(self.get_argument("limit", 50)) if limit > 200: limit = 200 open_filter = self.get_argument("filter", "Open Audits") audits = get_audits(self.session, only_open=(open_filter == "Open Audits")) open_audits = any([not audit.complete for audit in audits]) total = audits.count() audits = audits.offset(offset).limit(limit).all() open_audits = self.session.query(Audit).filter( Audit.complete == False).all() can_start = user_has_permission(self.session, user, AUDIT_MANAGER) # FIXME(herb): make limit selected from ui audit_log_entries = AuditLog.get_entries(self.session, category=AuditLogCategory.audit, limit=100) self.render( "audits.html", audits=audits, open_filter=open_filter, can_start=can_start, offset=offset, limit=limit, total=total, open_audits=open_audits, audit_log_entries=audit_log_entries, )
def test_expire_edges(expired_graph, session): # noqa """ Test expiration auditing and notification. """ email = session.query(AsyncNotification).all() assert email == [] for edge in session.query(GroupEdge).all(): assert edge.active == True # Expire the edges. background = BackgroundThread(settings, None) background.expire_edges(session) # Check that the edges are now marked as inactive. edges = session.query(GroupEdge).filter( GroupEdge.group_id == Group.id, Group.enabled == True, GroupEdge.expiration != None ).all() for edge in edges: assert edge.active == False # Check that we have two queued email messages. # # TODO(rra): It would be nice to check the contents as well. email = session.query(AsyncNotification).all() assert len(email) == 2 # Check that we have three audit log entries: one for the expired user and # two for both "sides" of the expired group membership. audits = AuditLog.get_entries(session, action="expired_from_group") assert len(audits) == 3
def test_expire_edges(expired_graph, session): # noqa: F811 """ Test expiration auditing and notification. """ email = session.query(AsyncNotification).all() assert email == [] for edge in session.query(GroupEdge).all(): assert edge.active == True # Expire the edges. background = BackgroundProcessor(settings, None) background.expire_edges(session) # Check that the edges are now marked as inactive. edges = (session.query(GroupEdge).filter( GroupEdge.group_id == Group.id, Group.enabled == True, GroupEdge.expiration != None).all()) for edge in edges: assert edge.active == False # Check that we have two queued email messages. # # TODO(rra): It would be nice to check the contents as well. email = session.query(AsyncNotification).all() assert len(email) == 2 # Check that we have three audit log entries: one for the expired user and # two for both "sides" of the expired group membership. audits = AuditLog.get_entries(session, action="expired_from_group") assert len(audits) == 3
def my_log_entries(self): # type: () -> List[AuditLog] """Returns the 20 most recent audit log entries involving this tag Returns: a list of AuditLog entries """ return AuditLog.get_entries(self.session, on_tag_id=self.id, limit=20)
def get_log_entries_by_user(session, user, limit=20): """For a given user, return the audit logs that pertain. Args: session(models.base.session.Session): database session user(User): user in question limit(int): number of results to return """ return AuditLog.get_entries(session, involve_user_id=user.id, limit=limit)
def get_log_entries_by_permission(session, permission, limit=20): """For a given permission, return the audit logs that pertain. Args: session(models.base.session.Session): database session permission_name(Permission): permission in question limit(int): number of results to return """ return AuditLog.get_entries(session, on_permission_id=permission.id, limit=limit)
def test_github(session, users, http_client, base_url): # noqa: F811 user = users["*****@*****.**"] assert get_user_metadata_by_key(session, user.id, USER_METADATA_GITHUB_USERNAME_KEY) is None user = User.get(session, name=user.username) fe_url = url(base_url, "/users/{}/github".format(user.username)) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"username": "******"}), headers={"X-Grouper-User": user.username}, ) assert resp.code == 200 user = User.get(session, name=user.username) assert (get_user_metadata_by_key(session, user.id, USER_METADATA_GITHUB_USERNAME_KEY) is not None) assert (get_user_metadata_by_key( session, user.id, USER_METADATA_GITHUB_USERNAME_KEY).data_value == "joe-on-github") audit_entries = AuditLog.get_entries(session, on_user_id=user.id, action="changed_github_username") assert len(audit_entries) == 1 assert audit_entries[ 0].description == "Changed GitHub username: joe-on-github" fe_url = url(base_url, "/users/{}/github".format(user.username)) resp = yield http_client.fetch( fe_url, method="POST", body=urlencode({"username": ""}), headers={"X-Grouper-User": user.username}, ) assert resp.code == 200 user = User.get(session, name=user.username) assert get_user_metadata_by_key(session, user.id, USER_METADATA_GITHUB_USERNAME_KEY) is None
def my_log_entries(self): return AuditLog.get_entries(self.session, involve_user_id=self.id, limit=20)
def my_log_entries(self): return AuditLog.get_entries(self.session, on_group_id=self.id, limit=20)
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_expire_nonauditors(standard_graph, users, groups, session, permissions): """ Test expiration auditing and notification. """ graph = standard_graph # noqa # Test audit autoexpiration for all approvers approver_roles = ["owner", "np-owner", "manager"] for role in approver_roles: # Add non-auditor as an owner to an audited group add_member(groups["audited-team"], users["*****@*****.**"], role=role) session.commit() graph.update_from_db(session) group_md = graph.get_group_details("audited-team") assert group_md.get('audited', False) # Expire the edges. background = BackgroundThread(settings, None) background.expire_nonauditors(session) # Check that the edges are now marked as inactive. edge = session.query(GroupEdge).filter_by(group_id=groups["audited-team"].id, member_pk=users["*****@*****.**"].id).scalar() assert edge.expiration is not None assert edge.expiration < datetime.utcnow() + timedelta(days=settings.nonauditor_expiration_days) assert edge.expiration > datetime.utcnow() + timedelta(days=settings.nonauditor_expiration_days - 1) assert any(["Subject: Membership in audited-team set to expire" 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_flagged") assert len(audits) == 3 + 1 * (approver_roles.index(role) + 1) revoke_member(groups["audited-team"], users["*****@*****.**"]) # Ensure nonauditor, nonapprovers in audited groups do not get set to expired member_roles = ["member"] for role in member_roles: # Add non-auditor as an owner to an audited group add_member(groups["audited-team"], users["*****@*****.**"], role=role) session.commit() graph.update_from_db(session) group_md = graph.get_group_details("audited-team") assert group_md.get('audited', False) # Expire the edges. background = BackgroundThread(settings, None) background.expire_nonauditors(session) # Check that the edges are now marked as inactive. edge = session.query(GroupEdge).filter_by(group_id=groups["audited-team"].id, member_pk=users["*****@*****.**"].id).scalar() assert edge.expiration is None assert not any(["Subject: Membership in audited-team set to expire" 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_flagged") assert len(audits) == 3 + 1 * len(approver_roles) revoke_member(groups["audited-team"], users["*****@*****.**"])
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_github(session, users, http_client, base_url, mocker): # noqa: F811 user = users["*****@*****.**"] assert get_user_metadata_by_key(session, user.id, USER_METADATA_GITHUB_USERNAME_KEY) is None user = User.get(session, name=user.username) fe_url = url(base_url, "/github/link_begin/{}".format(user.id)) mocker.patch.object(settings(), "github_app_client_id", "a-client-id") resp = yield http_client.fetch( fe_url, method="GET", headers={"X-Grouper-User": user.username}, follow_redirects=False, raise_error=False, ) assert resp.code == 302 redir_url = urlparse(resp.headers["Location"]) assert redir_url.netloc == "github.com" assert redir_url.path == "/login/oauth/authorize" query_params = parse_qs(redir_url.query) assert query_params["client_id"] == ["a-client-id"] (state, ) = query_params["state"] assert "github-link-state={}".format(state) in resp.headers["Set-cookie"] assert query_params["redirect_uri"] == [ "http://127.0.0.1:8888/github/link_complete/{}".format(user.id) ] fe_url = url( base_url, "/github/link_complete/{}?code=tempcode&state={}".format( user.id, state)) with pytest.raises(HTTPError) as excinfo: yield http_client.fetch( fe_url, method="GET", headers={ "X-Grouper-User": user.username, "Cookie": "github-link-state=bogus-state" }, ) assert excinfo.value.code == 400 recorder = FakeGitHubHttpClient() proxy_plugin = PluginProxy([SecretPlugin()]) mocker.patch("grouper.fe.handlers.github._get_github_http_client", lambda: recorder) mocker.patch("grouper.fe.handlers.github.get_plugin_proxy", lambda: proxy_plugin) mocker.patch.object(settings(), "http_proxy_host", "proxy-server") mocker.patch.object(settings(), "http_proxy_port", 42) resp = yield http_client.fetch( fe_url, method="GET", headers={ "X-Grouper-User": user.username, "Cookie": "github-link-state=" + state }, ) authorize_request, user_request = recorder.requests assert authorize_request.proxy_host == "proxy-server" assert authorize_request.proxy_port == 42 assert user_request.proxy_host == "proxy-server" assert user_request.proxy_port == 42 authorize_params = parse_qs(authorize_request.body) assert authorize_params[b"code"] == [b"tempcode"] assert authorize_params[b"state"] == [state.encode("ascii")] assert authorize_params[b"client_id"] == [b"a-client-id"] assert authorize_params[b"client_secret"] == [b"client-secret"] assert user_request.headers["Authorization"] == "token a-access-token" assert (get_user_metadata_by_key(session, user.id, USER_METADATA_GITHUB_USERNAME_KEY) is not None) assert (get_user_metadata_by_key( session, user.id, USER_METADATA_GITHUB_USERNAME_KEY).data_value == "zorkian-on-gh") audit_entries = AuditLog.get_entries(session, on_user_id=user.id, action="changed_github_username") assert len(audit_entries) == 1 assert audit_entries[ 0].description == "Changed GitHub username: zorkian-on-gh" fe_url = url(base_url, "/users/{}/github/clear".format(user.username)) resp = yield http_client.fetch(fe_url, method="POST", headers={"X-Grouper-User": user.username}, body=b"") assert resp.code == 200 assert get_user_metadata_by_key(session, user.id, USER_METADATA_GITHUB_USERNAME_KEY) is None audit_entries = AuditLog.get_entries(session, on_user_id=user.id, action="changed_github_username") assert len(audit_entries) == 2 audit_entries.sort(key=operator.attrgetter("id")) assert audit_entries[1].description == "Cleared GitHub link"
def test_expire_nonauditors(standard_graph, users, groups, session, permissions): """ Test expiration auditing and notification. """ graph = standard_graph # noqa # Test audit autoexpiration for all approvers approver_roles = ["owner", "np-owner", "manager"] for role in approver_roles: # Add non-auditor as an owner to an audited group add_member(groups["audited-team"], users["*****@*****.**"], role=role) session.commit() graph.update_from_db(session) group_md = graph.get_group_details("audited-team") assert group_md.get('audited', False) # Expire the edges. background = BackgroundProcessor(settings, None) background.expire_nonauditors(session) # Check that the edges are now marked as inactive. edge = session.query(GroupEdge).filter_by(group_id=groups["audited-team"].id, member_pk=users["*****@*****.**"].id).scalar() assert edge.expiration is not None assert edge.expiration < datetime.utcnow() + timedelta(days=settings.nonauditor_expiration_days) assert edge.expiration > datetime.utcnow() + timedelta(days=settings.nonauditor_expiration_days - 1) assert any(["Subject: Membership in audited-team set to expire" 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_flagged") assert len(audits) == 3 + 1 * (approver_roles.index(role) + 1) revoke_member(groups["audited-team"], users["*****@*****.**"]) # Ensure nonauditor, nonapprovers in audited groups do not get set to expired member_roles = ["member"] for role in member_roles: # Add non-auditor as an owner to an audited group add_member(groups["audited-team"], users["*****@*****.**"], role=role) session.commit() graph.update_from_db(session) group_md = graph.get_group_details("audited-team") assert group_md.get('audited', False) # Expire the edges. background = BackgroundProcessor(settings, None) background.expire_nonauditors(session) # Check that the edges are now marked as inactive. edge = session.query(GroupEdge).filter_by(group_id=groups["audited-team"].id, member_pk=users["*****@*****.**"].id).scalar() assert edge.expiration is None assert not any(["Subject: Membership in audited-team set to expire" 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_flagged") assert len(audits) == 3 + 1 * len(approver_roles) revoke_member(groups["audited-team"], users["*****@*****.**"])
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_audit_end_to_end(session, users, groups, http_client, base_url, graph): # noqa """ Tests an end-to-end audit cycle. """ groupname = 'audited-team' zay_id = users["*****@*****.**"].id 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, x.group.my_owners().iterkeys().next(), 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 = one_audit.group.my_owners().iterkeys().next() 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'