def update_status(self, requester, status, reason): now = datetime.utcnow() current_status = self.status self.status = status request_status_change = RequestStatusChange(request=self, user_id=requester.id, from_status=current_status, to_status=status, change_at=now).add( self.session) self.session.flush() Comment(obj_type=OBJ_TYPES_IDX.index("RequestStatusChange"), obj_pk=request_status_change.id, user_id=requester.id, comment=reason, created_on=now).add(self.session) if status == "actioned": edge = self.session.query(GroupEdge).filter_by( id=self.edge_id).one() edge.apply_changes(self) Counter.incr(self.session, "updates")
def cancel_user_request(self, user_request, reason, authorization): # type: (UserGroupRequest, str, Authorization) -> None now = datetime.utcnow() request = Request.get(self.session, id=user_request.id) if not request: raise UserGroupRequestNotFoundException(request) actor = User.get(self.session, name=authorization.actor) if not actor: raise UserNotFoundException(authorization.actor) request_status_change = RequestStatusChange( request=request, user_id=actor.id, from_status=request.status, to_status="cancelled", change_at=now, ).add(self.session) request.status = "cancelled" self.session.flush() Comment( obj_type=OBJ_TYPES["RequestStatusChange"], obj_pk=request_status_change.id, user_id=actor.id, comment=reason, created_on=now, ).add(self.session)
def revoke_member(self, requester, user_or_group, reason): """ Revoke a member (User or Group) from this group. Arguments: requester: A User object of the person requesting the addition user_or_group: A User/Group object of the member reason: A comment on why this member should exist """ now = datetime.utcnow() logging.debug( "Revoking member (%s) from %s", user_or_group.name, self.groupname ) # Create the edge even if it doesn't exist so that we can explicitly # disable it. edge, new = GroupEdge.get_or_create( self.session, group_id=self.id, member_type=user_or_group.member_type, member_pk=user_or_group.id, ) self.session.flush() request = Request( requester_id=requester.id, requesting_id=self.id, on_behalf_obj_type=user_or_group.member_type, on_behalf_obj_pk=user_or_group.id, requested_at=now, edge_id=edge.id, status="actioned", changes=build_changes( edge, role="member", expiration=None, active=False ) ).add(self.session) self.session.flush() request_status_change = RequestStatusChange( request=request, user_id=requester.id, to_status="actioned", change_at=now ).add(self.session) self.session.flush() Comment( obj_type=OBJ_TYPES_IDX.index("RequestStatusChange"), obj_pk=request_status_change.id, user_id=requester.id, comment=reason, created_on=now ).add(self.session) edge.apply_changes(request) self.session.flush() Counter.incr(self.session, "updates")
def update_request(session, request, user, new_status, comment): """Update a request. Args: session(sqlalchemy.orm.session.Session): database session request(models.PermissionRequest): request to update user(models.User): user making update new_status(models.base.constants.REQUEST_STATUS_CHOICES): new status comment(str): comment to include with status change Raises: grouper.audit.UserNotAuditor in case we're trying to add an audited permission to a group without auditors """ if request.status == new_status: # nothing to do return # make sure the grant can happen if new_status == "actioned": if request.permission.audited: # will raise UserNotAuditor if no auditors are owners of the group assert_controllers_are_auditors(request.group) # all rows we add have the same timestamp now = datetime.utcnow() # new status change row permission_status_change = PermissionRequestStatusChange( request=request, user_id=user.id, from_status=request.status, to_status=new_status, change_at=now, ).add(session) session.flush() # new comment Comment( obj_type=OBJ_TYPES_IDX.index("PermissionRequestStatusChange"), obj_pk=permission_status_change.id, user_id=user.id, comment=comment, created_on=now, ).add(session) # update permissionRequest status request.status = new_status session.commit() if new_status == "actioned": # actually grant permission try: grant_permission(session, request.group.id, request.permission.id, request.argument) except IntegrityError: session.rollback() # audit log AuditLog.log( session, user.id, "update_perm_request", "updated permission request to status: {}".format(new_status), on_group_id=request.group_id, on_user_id=request.requester_id, on_permission_id=request.permission.id, ) session.commit() # send notification subj_template = 'email/pending_permission_request_subj.tmpl' subject = "Re: " + get_template_env().get_template(subj_template).render( permission=request.permission.name, group=request.group.name ) if new_status == "actioned": email_template = "permission_request_actioned" else: email_template = "permission_request_cancelled" email_context = { 'group_name': request.group.name, 'action_taken_by': user.name, 'reason': comment, 'permission_name': request.permission.name, 'argument': request.argument, } send_email(session, [request.requester.name], subject, email_template, settings, email_context)
def create_request(session, user, group, permission, argument, reason): """ Creates an permission request and sends notification to the responsible approvers. Args: session(sqlalchemy.orm.session.Session): database session user(models.User): user requesting permission group(models.Group): group requested permission would be applied to permission(models.Permission): permission in question to request argument(str): argument for the given permission reason(str): reason the permission should be granted Raises: RequestAlreadyExists if trying to create a request that is already pending NoOwnersAvailable if no owner is available for the requested perm + arg. """ # check if group already has perm + arg pair for _, existing_perm_name, _, existing_perm_argument, _ in group.my_permissions(): if permission.name == existing_perm_name and argument == existing_perm_argument: raise RequestAlreadyGranted() # check if request already pending for this perm + arg pair existing_count = session.query(PermissionRequest).filter( PermissionRequest.group_id == group.id, PermissionRequest.permission_id == permission.id, PermissionRequest.argument == argument, PermissionRequest.status == "pending", ).count() if existing_count > 0: raise RequestAlreadyExists() # determine owner(s) owners_by_arg_by_perm = get_owners_by_grantable_permission(session, separate_global=True) owner_arg_list = get_owner_arg_list( session, permission, argument, owners_by_arg_by_perm=owners_by_arg_by_perm, ) if not owner_arg_list: raise NoOwnersAvailable() pending_status = "pending" now = datetime.utcnow() # multiple steps to create the request request = PermissionRequest( requester_id=user.id, group_id=group.id, permission_id=permission.id, argument=argument, status=pending_status, requested_at=now, ).add(session) session.flush() request_status_change = PermissionRequestStatusChange( request=request, user=user, to_status=pending_status, change_at=now, ).add(session) session.flush() Comment( obj_type=OBJ_TYPES_IDX.index("PermissionRequestStatusChange"), obj_pk=request_status_change.id, user_id=user.id, comment=reason, created_on=now, ).add(session) # send notification email_context = { "user_name": user.name, "group_name": group.name, "permission_name": permission.name, "argument": argument, "reason": reason, "request_id": request.id, "references_header": request.reference_id, } # TODO: would be nicer if it told you which group you're an approver of # that's causing this notification mail_to = [] global_owners = owners_by_arg_by_perm[GLOBAL_OWNERS]["*"] non_wildcard_owners = filter(lambda grant: grant[1] != '*', owner_arg_list) non_global_owners = filter(lambda grant: grant[0] not in global_owners, owner_arg_list) if any(non_wildcard_owners): # non-wildcard owners should get all the notifications mailto_owner_arg_list = non_wildcard_owners elif any(non_global_owners): mailto_owner_arg_list = non_global_owners else: # only the wildcards so they get the notifications mailto_owner_arg_list = owner_arg_list for owner, arg in mailto_owner_arg_list: mail_to += [u for t, u in owner.my_members() if t == 'User'] subj = get_template_env().get_template('email/pending_permission_request_subj.tmpl').render( permission=permission.name, group=group.name ) send_email(session, set(mail_to), subj, "pending_permission_request", settings, email_context) return request
def persist_group_member_changes( session, # type: Session group, # type: Group requester, # type: User member, # type: Union[User, Group] status, # type: str reason, # type: str create_edge=False, # type: bool **updates # type: Any ): # type: (...) -> Request requested_at = datetime.utcnow() if "role" in updates: role = updates["role"] _validate_role(member.member_type, role) get_plugin_proxy().will_update_group_membership(session, group, member, **updates) if create_edge: edge = _create_edge(session, group, member, updates.get("role", "member")) else: edge = _get_edge(session, group, member) if not edge: raise MemberNotFound() changes = _serialize_changes(edge, **updates) request = Request( requester_id=requester.id, requesting_id=group.id, on_behalf_obj_type=member.member_type, on_behalf_obj_pk=member.id, requested_at=requested_at, edge_id=edge.id, status=status, changes=changes, ).add(session) session.flush() request_status_change = RequestStatusChange( request=request, user_id=requester.id, to_status=status, change_at=requested_at).add(session) session.flush() Comment( obj_type=OBJ_TYPES["RequestStatusChange"], obj_pk=request_status_change.id, user_id=requester.id, comment=reason, created_on=requested_at, ).add(session) session.flush() if status == "actioned": edge.apply_changes(request.changes) session.flush() Counter.incr(session, "updates") return request
def update_request(session: Session, request: PermissionRequest, user: User, new_status: str, comment: str) -> None: """Update a request. Args: session: Database session request: Request to update user: User making update new_status: New status comment: Comment to include with status change Raises: grouper.audit.UserNotAuditor in case we're trying to add an audited permission to a group without auditors """ if request.status == new_status: # nothing to do return # make sure the grant can happen if new_status == "actioned": if request.permission.audited: # will raise UserNotAuditor if no auditors are owners of the group assert_controllers_are_auditors(request.group) # all rows we add have the same timestamp now = datetime.utcnow() # new status change row permission_status_change = PermissionRequestStatusChange( request=request, user_id=user.id, from_status=request.status, to_status=new_status, change_at=now, ).add(session) session.flush() # new comment Comment( obj_type=OBJ_TYPES_IDX.index("PermissionRequestStatusChange"), obj_pk=permission_status_change.id, user_id=user.id, comment=comment, created_on=now, ).add(session) # update permissionRequest status request.status = new_status session.commit() if new_status == "actioned": # actually grant permission try: grant_permission(session, request.group.id, request.permission.id, request.argument) except IntegrityError: session.rollback() # audit log AuditLog.log( session, user.id, "update_perm_request", "updated permission request to status: {}".format(new_status), on_group_id=request.group_id, on_user_id=request.requester_id, on_permission_id=request.permission.id, ) session.commit() # send notification template_engine = EmailTemplateEngine(settings()) subject_template = template_engine.get_template( "email/pending_permission_request_subj.tmpl") subject = "Re: " + subject_template.render( permission=request.permission.name, group=request.group.name) if new_status == "actioned": email_template = "permission_request_actioned" else: email_template = "permission_request_cancelled" email_context = { "group_name": request.group.name, "action_taken_by": user.name, "reason": comment, "permission_name": request.permission.name, "argument": request.argument, } send_email(session, [request.requester.name], subject, email_template, settings(), email_context)
def create_request(session: Session, user: User, group: Group, permission: Permission, argument: str, reason: str) -> PermissionRequest: """Creates an permission request and sends notification to the responsible approvers. Args: session: Database session user: User requesting permission group: Group requested permission would be applied to permission: Permission in question to request argument: argument for the given permission reason: reason the permission should be granted Raises: RequestAlreadyExists: Trying to create a request that is already pending NoOwnersAvailable: No owner is available for the requested perm + arg. grouper.audit.UserNotAuditor: The group has owners that are not auditors """ # check if group already has perm + arg pair for _, existing_perm_name, _, existing_perm_argument, _ in group.my_permissions( ): if permission.name == existing_perm_name and argument == existing_perm_argument: raise RequestAlreadyGranted() # check if request already pending for this perm + arg pair existing_count = (session.query(PermissionRequest).filter( PermissionRequest.group_id == group.id, PermissionRequest.permission_id == permission.id, PermissionRequest.argument == argument, PermissionRequest.status == "pending", ).count()) if existing_count > 0: raise RequestAlreadyExists() # determine owner(s) owners_by_arg_by_perm = get_owners_by_grantable_permission( session, separate_global=True) owner_arg_list = get_owner_arg_list( session, permission, argument, owners_by_arg_by_perm=owners_by_arg_by_perm) if not owner_arg_list: raise NoOwnersAvailable() if permission.audited: # will raise UserNotAuditor if any owner of the group is not an auditor assert_controllers_are_auditors(group) pending_status = "pending" now = datetime.utcnow() # multiple steps to create the request request = PermissionRequest( requester_id=user.id, group_id=group.id, permission_id=permission.id, argument=argument, status=pending_status, requested_at=now, ).add(session) session.flush() request_status_change = PermissionRequestStatusChange( request=request, user=user, to_status=pending_status, change_at=now).add(session) session.flush() Comment( obj_type=OBJ_TYPES_IDX.index("PermissionRequestStatusChange"), obj_pk=request_status_change.id, user_id=user.id, comment=reason, created_on=now, ).add(session) # send notification email_context = { "user_name": user.name, "group_name": group.name, "permission_name": permission.name, "argument": argument, "reason": reason, "request_id": request.id, "references_header": request.reference_id, } # TODO: would be nicer if it told you which group you're an approver of # that's causing this notification mail_to = [] global_owners = owners_by_arg_by_perm[GLOBAL_OWNERS]["*"] non_wildcard_owners = [ grant for grant in owner_arg_list if grant[1] != "*" ] non_global_owners = [ grant for grant in owner_arg_list if grant[0] not in global_owners ] if any(non_wildcard_owners): # non-wildcard owners should get all the notifications mailto_owner_arg_list = non_wildcard_owners elif any(non_global_owners): mailto_owner_arg_list = non_global_owners else: # only the wildcards so they get the notifications mailto_owner_arg_list = owner_arg_list for owner, arg in mailto_owner_arg_list: if owner.email_address: mail_to.append(owner.email_address) else: mail_to.extend([u for t, u in owner.my_members() if t == "User"]) template_engine = EmailTemplateEngine(settings()) subject_template = template_engine.get_template( "email/pending_permission_request_subj.tmpl") subject = subject_template.render(permission=permission.name, group=group.name) send_email(session, set(mail_to), subject, "pending_permission_request", settings(), email_context) return request
def add_member(self, requester, user_or_group, reason, status="pending", expiration=None, role="member"): """ Add a member (User or Group) to this group. Arguments: requester: A User object of the person requesting the addition user_or_group: A User/Group object of the member reason: A comment on why this member should exist status: pending/actioned, whether the request needs approval or should be immediate expiration: datetime object when membership should expire. role: member/manager/owner/np-owner of the Group. """ now = datetime.utcnow() member_type = user_or_group.member_type if member_type == 1 and role != "member": raise InvalidRoleForMember("Groups can only have the role of 'member'") logging.debug( "Adding member (%s) to %s", user_or_group.name, self.groupname ) edge, new = GroupEdge.get_or_create( self.session, group_id=self.id, member_type=member_type, member_pk=user_or_group.id, ) # TODO(herb): this means all requests by this user to this group will # have the same role. we should probably record the role specifically # on the request and use that as the source on the UI edge._role = GROUP_EDGE_ROLES.index(role) self.session.flush() request = Request( requester_id=requester.id, requesting_id=self.id, on_behalf_obj_type=member_type, on_behalf_obj_pk=user_or_group.id, requested_at=now, edge_id=edge.id, status=status, changes=build_changes( edge, role=role, expiration=expiration, active=True ) ).add(self.session) self.session.flush() request_status_change = RequestStatusChange( request=request, user_id=requester.id, to_status=status, change_at=now ).add(self.session) self.session.flush() Comment( obj_type=3, obj_pk=request_status_change.id, user_id=requester.id, comment=reason, created_on=now ).add(self.session) if status == "actioned": edge.apply_changes(request) self.session.flush() Counter.incr(self.session, "updates") return request.id
def edit_member(self, requester, user_or_group, reason, **kwargs): """ Edit an existing member (User or Group) of a group. This takes the same parameters as add_member, except that we do not allow you to set a status: this only works on existing members. Any option that is not passed is not updated, and instead, the existing value for this user is kept. """ now = datetime.utcnow() member_type = user_or_group.member_type if member_type == 1 and "role" in kwargs and kwargs["role"] != "member": raise InvalidRoleForMember("Groups can only have the role of 'member'") logging.debug( "Editing member (%s) in %s", user_or_group.name, self.groupname ) edge = GroupEdge.get( self.session, group_id=self.id, member_type=member_type, member_pk=user_or_group.id, ) self.session.flush() if not edge: raise MemberNotFound() request = Request( requester_id=requester.id, requesting_id=self.id, on_behalf_obj_type=member_type, on_behalf_obj_pk=user_or_group.id, requested_at=now, edge_id=edge.id, status="actioned", changes=build_changes( edge, **kwargs ), ).add(self.session) self.session.flush() request_status_change = RequestStatusChange( request=request, user_id=requester.id, to_status="actioned", change_at=now, ).add(self.session) self.session.flush() Comment( obj_type=OBJ_TYPES_IDX.index("RequestStatusChange"), obj_pk=request_status_change.id, user_id=requester.id, comment=reason, created_on=now, ).add(self.session) edge.apply_changes(request) self.session.flush() message = "Edit member {} {}: {}".format( OBJ_TYPES_IDX[member_type].lower(), user_or_group.name, reason) AuditLog.log(self.session, requester.id, 'edit_member', message, on_group_id=self.id) Counter.incr(self.session, "updates")