def _get_form_alerts(self, errors: Dict[str, List[str]]) -> List[Alert]: """Create alerts from all errors in a WTForms form.""" alerts = [] for field, field_errors in errors.items(): for error in field_errors: alerts.append(Alert("danger", error, field)) return alerts
def post(self, request_id): # check for request existence request = permissions.get_request_by_id(self.session, request_id) if not request: return self.notfound() # check that this user should be actioning this request user_requests, total = permissions.get_requests( self.session, status="pending", limit=None, offset=0, owner=self.current_user) user_request_ids = [ur.id for ur in user_requests.requests] if request.id not in user_request_ids: return self.forbidden() form = PermissionRequestUpdateForm(self.request.arguments) form.status.choices = self._get_choices(request.status) if not form.validate(): change_comment_list = [ (sc, user_requests.comment_by_status_change_id[sc.id]) for sc in user_requests.status_change_by_request_id[request.id] ] return self.render( "permission-request-update.html", form=form, request=request, change_comment_list=change_comment_list, statuses=REQUEST_STATUS_CHOICES, alerts=self.get_form_alerts(form.errors), ) try: permissions.update_request(self.session, request, self.current_user, form.status.data, form.reason.data) except UserNotAuditor as e: alerts = [Alert("danger", str(e))] change_comment_list = [ (sc, user_requests.comment_by_status_change_id[sc.id]) for sc in user_requests.status_change_by_request_id[request.id] ] return self.render( "permission-request-update.html", form=form, request=request, change_comment_list=change_comment_list, statuses=REQUEST_STATUS_CHOICES, alerts=alerts, ) return self.redirect("/permissions/requests?status=pending")
def disable_permission_failed_existing_grants( self, name: str, group_grants: List[GroupPermissionGrant], service_account_grants: List[ServiceAccountPermissionGrant], ) -> None: """The permission view page will show the grants, so we don't include them in the error.""" alert = Alert( "danger", ( "Permission cannot be disabled while it is still granted to some groups or" " service accounts" ), ) self.redirect("/permissions/{}".format(name), alerts=[alert])
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") user = User.get(self.session, name=name) if not user: return self.notfound() if not self.check_access(self.session, self.current_user, user): return self.forbidden() try: if user.role_user: disable_role_user(self.session, user=user) else: disable_user(self.session, user) except PluginRejectedDisablingUser as e: alert = Alert("danger", str(e)) return self.redirect("/users/{}".format(user.name), alerts=[alert]) self.session.commit() AuditLog.log( self.session, self.current_user.id, "disable_user", "Disabled user.", on_user_id=user.id, ) if user.role_user: group = Group.get(self.session, name=user.username) if group and group.audit: # complete the audit group.audit.complete = True self.session.commit() cancel_async_emails(self.session, f"audit-{group.id}") AuditLog.log( self.session, self.current_user.id, "complete_audit", "Disabling group completes group audit.", on_group_id=group.id, ) return self.redirect("/users/{}?refresh=yes".format(user.name))
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") group = Group.get(self.session, name=name) if not group: return self.notfound() if not user_can_manage_group(self.session, group, self.current_user): return self.forbidden() form = GroupRemoveForm(self.request.arguments) if not form.validate(): return self.send_error(status_code=400) member_type, member_name = form.data["member_type"], form.data[ "member"] members = group.my_members() if not members.get((member_type.capitalize(), member_name), None): return self.send_error( status_code=400, reason="Invalid remove request: {} is not a member of {}.". format(member_name, group.name), ) removed_member = get_user_or_group(self.session, member_name, user_or_group=member_type) if self.current_user == removed_member: return self.send_error( status_code=400, reason="Can't remove yourself. Leave group instead.") role_user = is_role_user(self.session, group=group) if role_user and get_role_user( self.session, group=group).user.name == removed_member.name: return self.send_error( status_code=400, reason= "Can't remove a service account user from the service account group.", ) try: group.revoke_member(self.current_user, removed_member, "Removed by owner/np-owner/manager") AuditLog.log( self.session, self.current_user.id, "remove_from_group", "{} was removed from the group.".format(removed_member.name), on_group_id=group.id, on_user_id=removed_member.id, ) except PluginRejectedGroupMembershipUpdate as e: alert = Alert("danger", str(e)) if role_user: return self.redirect("/service/{}".format(group.name), alerts=[alert]) else: return self.redirect("/groups/{}".format(group.name), alerts=[alert]) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") group = Group.get(self.session, name=name) if not group or not group.enabled: return self.notfound() members = group.my_members() member_groups = {g for t, g in members if t == "Group"} user_is_member = self._is_user_a_member(group, members) form = GroupJoinForm(self.request.arguments) form.member.choices = self._get_choices(group, member_groups, user_is_member) if not form.validate(): return self.render("group-join.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)) member = self._get_member(form.data["member"]) if not member: return self.render( "group-join.html", form=form, group=group, alerts=[ Alert( "danger", "Unknown user or group: {}".format( form.data["member"])) ], ) try: assert_can_join(group, member, role=form.data["role"]) except UserNotAuditor as e: return self.render( "group-join.html", form=form, group=group, alerts=[Alert("danger", str(e), "Audit Policy Enforcement")], ) if group.canjoin == "nobody": fail_message = "This group cannot be joined at this time." return self.render("group-join.html", form=form, group=group, alerts=[Alert("danger", fail_message)]) if group.require_clickthru_tojoin: if not form.data["clickthru_agreement"]: return self.render( "group-join.html", form=form, group=group, alerts=[ Alert( "danger", "please accept review of the group's description", "Clickthru Enforcement", ) ], ) # We only use the default expiration time if no expiration time was given # This does mean that if a user wishes to join a group with no expiration # (even with an owner's permission) that has an auto expiration, they must # first be accepted to the group and then have the owner edit the user to # have no expiration. expiration = None if form.data["expiration"]: expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y") elif group.auto_expire: expiration = datetime.utcnow() + group.auto_expire # If the requested role is member, set the status based on the group's canjoin setting, # which automatically actions the request if the group can be joined by anyone and # otherwise sets it pending. # # However, we don't want to let people autojoin as owner or np-owner even to otherwise open # groups, so if the role is not member, force the status to pending. if form.data["role"] == "member": status = GROUP_JOIN_CHOICES[group.canjoin] else: status = "pending" try: request = group.add_member( requester=self.current_user, user_or_group=member, reason=form.data["reason"], status=status, expiration=expiration, role=form.data["role"], ) except InvalidRoleForMember as e: return self.render( "group-join.html", form=form, group=group, alerts=[Alert("danger", str(e), "Invalid Role")], ) self.session.commit() if status == "pending": AuditLog.log( self.session, self.current_user.id, "join_group", "{} requested to join with role: {}".format( member.name, form.data["role"]), on_group_id=group.id, ) mail_to = [ user.name for user in group.my_users() if GROUP_EDGE_ROLES[user.role] in ("manager", "owner", "np-owner") ] email_context = { "requester": member.name, "requested_by": self.current_user.name, "request_id": request.id, "group_name": group.name, "reason": form.data["reason"], "expiration": expiration, "role": form.data["role"], "references_header": request.reference_id, } subj = self.render_template("email/pending_request_subj.tmpl", group=group.name, user=self.current_user.name) send_email(self.session, mail_to, subj, "pending_request", settings(), email_context) elif status == "actioned": AuditLog.log( self.session, self.current_user.id, "join_group", "{} auto-approved to join with role: {}".format( member.name, form.data["role"]), on_group_id=group.id, ) else: raise Exception(f"Unknown join status {status}") return self.redirect("/groups/{}?refresh=yes".format(group.name))
def _deserialize_alert(alert_dict: Dict[str, str]) -> Alert: return Alert( severity=alert_dict["severity"], message=alert_dict["message"], heading=alert_dict["heading"], )
def get_form_alerts(self, errors: Dict[str, List[str]]) -> List[Alert]: alerts = [] for field, field_errors in errors.items(): for error in field_errors: alerts.append(Alert("danger", error, field)) return alerts
def post(self, *args: Any, **kwargs: Any) -> None: request_id = self.get_path_argument("request_id") name = self.get_path_argument("name") group = Group.get(self.session, name=name) if not group: return self.notfound() members = group.my_members() my_role = user_role(self.current_user, members) if my_role not in ("manager", "owner", "np-owner"): return self.forbidden() request = self.session.query(Request).filter_by(id=request_id).scalar() if not request: return self.notfound() on_behalf = get_on_behalf_by_request(self.session, request) form = GroupRequestModifyForm(self.request.arguments) form.status.choices = self._get_choices(request.status) updates = request.my_status_updates() if not form.status.choices: alerts = [Alert("info", "Request has already been processed")] return self.render( "group-request-update.html", group=group, request=request, on_behalf=on_behalf, members=members, form=form, alerts=alerts, statuses=REQUEST_STATUS_CHOICES, updates=updates, ) if not form.validate(): return self.render( "group-request-update.html", group=group, request=request, on_behalf=on_behalf, members=members, form=form, alerts=self.get_form_alerts(form.errors), statuses=REQUEST_STATUS_CHOICES, updates=updates, ) # We have to test this here, too, to ensure that someone can't sneak in with a pending # request that used to be allowed. if form.data["status"] != "cancelled": try: assert_can_join(request.requesting, on_behalf, role=request.edge.role) except UserNotAuditor as e: return self.render( "group-request-update.html", group=group, request=request, on_behalf=on_behalf, members=members, form=form, statuses=REQUEST_STATUS_CHOICES, updates=updates, alerts=[ Alert("danger", str(e), "Audit Policy Enforcement") ], ) request.update_status(self.current_user, form.data["status"], form.data["reason"]) self.session.commit() AuditLog.log( self.session, self.current_user.id, "update_request", "Updated request to status: {}".format(form.data["status"]), on_group_id=group.id, on_user_id=request.requester.id, ) edge = self.session.query(GroupEdge).filter_by( id=request.edge_id).one() approver_mail_to = [ user.name for user in group.my_approver_users() if user.name != self.current_user.name and user.name != request.requester.username ] subj = "Re: " + self.render_template("email/pending_request_subj.tmpl", group=group.name, user=request.requester.username) send_email( self.session, approver_mail_to, subj, "approver_request_updated", settings(), { "group_name": group.name, "requester": request.requester.username, "changed_by": self.current_user.name, "status": form.data["status"], "role": edge.role, "reason": form.data["reason"], "references_header": request.reference_id, }, ) if form.data["status"] == "actioned": send_email( self.session, [request.requester.name], "Added to group: {}".format(group.groupname), "request_actioned", settings(), { "group_name": group.name, "actioned_by": self.current_user.name, "reason": form.data["reason"], "expiration": edge.expiration, "role": edge.role, }, ) elif form.data["status"] == "cancelled": send_email( self.session, [request.requester.name], "Request to join cancelled: {}".format(group.groupname), "request_cancelled", settings(), { "group_name": group.name, "cancelled_by": self.current_user.name, "reason": form.data["reason"], "expiration": edge.expiration, "role": edge.role, }, ) # No explicit refresh because handler queries SQL. if form.data["redirect_aggregate"]: return self.redirect("/user/requests") else: return self.redirect("/groups/{}/requests".format(group.name))
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") group = Group.get(self.session, name=name) if not group: return self.notfound() if not user_can_manage_group(self.session, group, self.current_user): return self.forbidden() members = group.my_members() my_role = user_role(self.current_user, members) form = self.get_form(role=my_role) if not form.validate(): return self.render("group-add.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)) member = get_user_or_group(self.session, form.data["member"]) if member.type == "User" and is_role_user(self.session, member): # For service accounts, we want to always add the group to other groups, not the user member = get_role_user(self.session, user=member).group if not member: form.member.errors.append("User or group not found.") elif (member.type, member.name) in group.my_members(): form.member.errors.append( "User or group is already a member of this group.") elif group.name == member.name: form.member.errors.append( "By definition, this group is a member of itself already.") # Ensure this doesn't violate auditing constraints try: assert_can_join(group, member, role=form.data["role"]) except UserNotAuditor as e: form.member.errors.append(str(e)) if form.member.errors: return self.render("group-add.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)) expiration = None if form.data["expiration"]: expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y") try: group.add_member( requester=self.current_user, user_or_group=member, reason=form.data["reason"], status="actioned", expiration=expiration, role=form.data["role"], ) except InvalidRoleForMember as e: return self.render("group-add.html", form=form, group=group, alerts=[Alert("danger", str(e))]) self.session.commit() on_user_id = member.id if member.type == "User" else None AuditLog.log( self.session, self.current_user.id, "join_group", "{} added to group with role: {}".format(member.name, form.data["role"]), on_group_id=group.id, on_user_id=on_user_id, ) if member.type == "User": send_email( self.session, [member.name], "Added to group: {}".format(group.name), "request_actioned", settings(), { "group_name": group.name, "actioned_by": self.current_user.name, "reason": form.data["reason"], "expiration": expiration, "role": form.data["role"], }, ) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def get_group_view_template_vars(session, actor, group, graph): # type: (Session, User, Group, GroupGraph) -> Dict[str, Any] ret = {} ret["grantable"] = user_grantable_permissions(session, actor) try: group_md = graph.get_group_details(group.name) except NoSuchGroup: # Very new group with no metadata yet, or it has been disabled and # excluded from in-memory cache. group_md = {} ret["members"] = group.my_members() ret["groups"] = group.my_groups() ret["service_accounts"] = get_service_accounts(session, group) ret["permissions"] = group_md.get("permissions", []) for permission in ret["permissions"]: permission["granted_on"] = datetime.fromtimestamp(permission["granted_on"]) ret["permission_requests_pending"] = [] for req in get_pending_request_by_group(session, group): granters = [] for owner, argument in get_owner_arg_list(session, req.permission, req.argument): granters.append(owner.name) ret["permission_requests_pending"].append((req, granters)) ret["audited"] = group_md.get("audited", False) ret["log_entries"] = group.my_log_entries() ret["num_pending"] = count_requests_by_group(session, group, status="pending") ret["current_user_role"] = { "is_owner": user_role_index(actor, ret["members"]) in OWNER_ROLE_INDICES, "is_approver": user_role_index(actor, ret["members"]) in APPROVER_ROLE_INDICES, "is_manager": user_role(actor, ret["members"]) == "manager", "is_member": user_role(actor, ret["members"]) is not None, "role": user_role(actor, ret["members"]), } ret["statuses"] = AUDIT_STATUS_CHOICES # The user can leave if they're a normal member, or if they're not the only owner. number_of_owners = len( [m for (t, m), o in ret["members"].items() if t == "User" and o.role in OWNER_ROLE_INDICES] ) ret["can_leave"] = ret["current_user_role"]["is_member"] and ( not ret["current_user_role"]["is_owner"] or number_of_owners > 1 ) # Add mapping_id to permissions structure ret["my_permissions"] = group.my_permissions() for perm_up in ret["permissions"]: for perm_direct in ret["my_permissions"]: if ( perm_up["permission"] == perm_direct.name and perm_up["argument"] == perm_direct.argument ): perm_up["mapping_id"] = perm_direct.mapping_id break ret["alerts"] = [] ret["self_pending"] = count_requests_by_group(session, group, status="pending", user=actor) if ret["self_pending"]: ret["alerts"].append(Alert("info", "You have a pending request to join this group.", None)) return ret
def post(self, *args: Any, **kwargs: Any) -> None: name = self.get_path_argument("name") member_name = self.get_path_argument("member_name") member_type = self.get_path_argument("member_type") group = Group.get(self.session, name=name) if not group: return self.notfound() members = group.my_members() my_role = user_role(self.current_user, members) if my_role not in ("manager", "owner", "np-owner"): return self.forbidden() member = members.get((member_type.capitalize(), member_name), None) if not member: return self.notfound() if member.type == "Group": user_or_group = Group.get(self.session, member.id) else: user_or_group = User.get(self.session, member.id) if not user_or_group: return self.notfound() edge = GroupEdge.get( self.session, group_id=group.id, member_type=OBJ_TYPES[member.type], member_pk=member.id, ) if not edge: return self.notfound() form = self._get_form(member_name, my_role, member_type) if not form.validate(): return self.render( "group-edit-member.html", group=group, member=member, edge=edge, form=form, alerts=self.get_form_alerts(form.errors), ) try: assert_can_join(group, user_or_group, role=form.data["role"]) except UserNotAuditor as e: return self.render( "group-edit-member.html", form=form, group=group, member=member, edge=edge, alerts=[Alert("danger", str(e), "Audit Policy Enforcement")], ) expiration = None if form.data["expiration"]: expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y") try: group.edit_member( self.current_user, user_or_group, form.data["reason"], role=form.data["role"], expiration=expiration, ) except (InvalidRoleForMember, PluginRejectedGroupMembershipUpdate) as e: return self.render( "group-edit-member.html", form=form, group=group, member=member, edge=edge, alerts=[Alert("danger", str(e))], ) return self.redirect("/groups/{}?refresh=yes".format(group.name))
def grant_permission_to_service_account_failed_permission_denied( self, permission: str, argument: str, service: str, message: str) -> None: alert = Alert("error", message) self._render_template(self._form, service, self._owner, [alert])
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))
def post(self, *args, **kwargs): # type: (*Any, **Any) -> None form, args_by_perm = self._build_form(self.request.arguments) if not form.validate(): return self.render( "permission-request.html", args_by_perm_json=json.dumps(args_by_perm), form=form, alerts=self.get_form_alerts(form.errors), ) group = Group.get(self.session, name=form.group_name.data) if group is None: raise HTTPError(status_code=400, reason="that group does not exist") permission = get_permission(self.session, form.permission.data) if permission is None: raise HTTPError(status_code=400, reason="that permission does not exist") if permission.name not in args_by_perm: raise HTTPError(status_code=400, reason="that permission was not in the form") argument = form.argument.data.strip() # save off request try: request = permissions.create_request(self.session, self.current_user, group, permission, argument, form.reason.data) except permissions.RequestAlreadyGranted: alerts = [ Alert("danger", "This group already has this permission and argument.") ] except permissions.RequestAlreadyExists: alerts = [ Alert( "danger", "Request for permission and argument already exists, please wait patiently.", ) ] except permissions.NoOwnersAvailable: self.log_message( "prefilled perm+arg have no owner", group_name=group.name, permission=permission.name, argument=argument, ) alerts = [ Alert( "danger", "No owners available for requested permission and argument." " If this error persists please contact an adminstrator.", ) ] except UserNotAuditor as e: alerts = [Alert("danger", str(e))] else: alerts = [] if alerts: return self.render( "permission-request.html", args_by_perm_json=json.dumps(args_by_perm), form=form, alerts=alerts, ) else: return self.redirect("/permissions/requests/{}".format(request.id))