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 post(self, audit_id): user = self.get_current_user() if not user_has_permission(self.session, user, PERMISSION_AUDITOR): return self.forbidden() audit = self.session.query(Audit).filter(Audit.id == audit_id).one() # only owners can complete owner_ids = {member.id for member in audit.group.my_owners().values()} if user.id not in owner_ids: return self.forbidden() if audit.complete: return self.redirect("/groups/{}".format(audit.group.name)) edges = {} for argument in self.request.arguments: if argument.startswith('audit_'): edges[int(argument.split('_')[1])] = self.request.arguments[argument][0] for member in audit.my_members(): if member.id in edges: # You can only approve yourself (otherwise you can remove yourself # from the group and leave it ownerless) if member.member.id == user.id: member.status = "approved" elif edges[member.id] in AUDIT_STATUS_CHOICES: member.status = edges[member.id] self.session.commit() # Now if it's completable (no pendings) then mark it complete, else redirect them # to the group page. if not audit.completable: return self.redirect('/groups/{}'.format(audit.group.name)) # Complete audits have to be "enacted" now. This means anybody marked as remove has to # be removed from the group now. for member in audit.my_members(): if member.status == "remove": audit.group.revoke_member(self.current_user, member.member, "Revoked as part of audit.") AuditLog.log(self.session, self.current_user.id, 'remove_member', 'Removed membership in audit: {}'.format(member.member.name), on_group_id=audit.group.id, on_user_id=member.member.id, category=AuditLogCategory.audit) audit.complete = True self.session.commit() # Now cancel pending emails cancel_async_emails(self.session, 'audit-{}'.format(audit.group.id)) AuditLog.log(self.session, self.current_user.id, 'complete_audit', 'Completed group audit.', on_group_id=audit.group.id, category=AuditLogCategory.audit) # check if all audits are complete if get_audits(self.session, only_open=True).count() == 0: AuditLog.log(self.session, self.current_user.id, 'complete_global_audit', 'last open audit have been completed', category=AuditLogCategory.audit) return self.redirect('/groups/{}'.format(audit.group.name))
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 post(self, audit_id): user = self.get_current_user() if not user_has_permission(self.session, user, PERMISSION_AUDITOR): return self.forbidden() audit = self.session.query(Audit).filter(Audit.id == audit_id).one() # only owners can complete owner_ids = {member.id for member in audit.group.my_owners().values()} if user.id not in owner_ids: return self.forbidden() if audit.complete: return self.redirect("/groups/{}".format(audit.group.name)) edges = {} for argument in self.request.arguments: if argument.startswith('audit_'): edges[int(argument.split('_') [1])] = self.request.arguments[argument][0] for member in audit.my_members(): if member.id in edges: # You can only approve yourself (otherwise you can remove yourself # from the group and leave it ownerless) if member.member.id == user.id: member.status = "approved" elif edges[member.id] in AUDIT_STATUS_CHOICES: member.status = edges[member.id] self.session.commit() # Now if it's completable (no pendings) then mark it complete, else redirect them # to the group page. if not audit.completable: return self.redirect('/groups/{}'.format(audit.group.name)) # Complete audits have to be "enacted" now. This means anybody marked as remove has to # be removed from the group now. try: for member in audit.my_members(): if member.status == "remove": audit.group.revoke_member(self.current_user, member.member, "Revoked as part of audit.") AuditLog.log(self.session, self.current_user.id, 'remove_member', 'Removed membership in audit: {}'.format( member.member.name), on_group_id=audit.group.id, on_user_id=member.member.id, category=AuditLogCategory.audit) except PluginRejectedGroupMembershipUpdate as e: alert = Alert("danger", str(e)) return self.redirect('/groups/{}'.format(audit.group.name), alerts=[alert]) audit.complete = True self.session.commit() # Now cancel pending emails cancel_async_emails(self.session, 'audit-{}'.format(audit.group.id)) AuditLog.log(self.session, self.current_user.id, 'complete_audit', 'Completed group audit.', on_group_id=audit.group.id, category=AuditLogCategory.audit) # check if all audits are complete if get_audits(self.session, only_open=True).count() == 0: AuditLog.log(self.session, self.current_user.id, 'complete_global_audit', 'last open audit have been completed', category=AuditLogCategory.audit) return self.redirect('/groups/{}'.format(audit.group.name))
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'
def post(self, audit_id): if not user_has_permission(self.session, self.current_user, PERMISSION_AUDITOR): return self.forbidden() audit = self.session.query(Audit).filter(Audit.id == audit_id).one() # only owners can complete owner_ids = {member.id for member in audit.group.my_owners().values()} if self.current_user.id not in owner_ids: return self.forbidden() if audit.complete: return self.redirect("/groups/{}".format(audit.group.name)) edges = {} for argument in self.request.arguments: if argument.startswith("audit_"): edges[int(argument.split("_") [1])] = self.request.arguments[argument][0].decode() for audit_member_info in get_group_audit_members_infos( self.session, audit.group): if audit_member_info.audit_member_obj.id in edges: # You can only approve yourself (otherwise you can remove yourself # from the group and leave it ownerless) if audit_member_info.member_obj.id == self.current_user.id: audit_member_info.audit_member_obj.status = "approved" elif edges[audit_member_info.audit_member_obj. id] in AUDIT_STATUS_CHOICES: audit_member_info.audit_member_obj.status = edges[ audit_member_info.audit_member_obj.id] self.session.commit() # If there are still pending statuses, then redirect to the group page. if group_has_pending_audit_members(self.session, audit.group): return self.redirect("/groups/{}".format(audit.group.name)) # Complete audits have to be "enacted" now. This means anybody marked as remove has to # be removed from the group now. try: for audit_member_info in get_group_audit_members_infos( self.session, audit.group): member_obj = audit_member_info.member_obj if audit_member_info.audit_member_obj.status == "remove": audit.group.revoke_member(self.current_user, member_obj, "Revoked as part of audit.") AuditLog.log( self.session, self.current_user.id, "remove_member", "Removed membership in audit: {}".format( member_obj.name), on_group_id=audit.group.id, on_user_id=member_obj.id, category=AuditLogCategory.audit, ) except PluginRejectedGroupMembershipUpdate as e: alert = Alert("danger", str(e)) return self.redirect("/groups/{}".format(audit.group.name), alerts=[alert]) audit.complete = True self.session.commit() # Now cancel pending emails cancel_async_emails(self.session, "audit-{}".format(audit.group.id)) AuditLog.log( self.session, self.current_user.id, "complete_audit", "Completed group audit.", on_group_id=audit.group.id, category=AuditLogCategory.audit, ) # check if all audits are complete if get_audits(self.session, only_open=True).count() == 0: AuditLog.log( self.session, self.current_user.id, "complete_global_audit", "last open audit have been completed", category=AuditLogCategory.audit, ) return self.redirect("/groups/{}".format(audit.group.name))