def get_requests_by_owner(session, owner, status, limit, offset): """Load pending requests for a particular owner. Args: session(sqlalchemy.orm.session.Session): database session owner(models.User): model of user in question status(models.REQUEST_STATUS_CHOICES): if not None, filter by particular status limit(int): how many results to return offset(int): the offset into the result set that should be applied Returns: 2-tuple of (Requests, total) where total is total result size and Requests is the namedtuple with requests and associated comments/changes. """ # get owners groups group_ids = {g.id for g, _ in get_groups_by_user(session, owner)} # get all requests all_requests = session.query(PermissionRequest) if status: all_requests = all_requests.filter(PermissionRequest.status == status) all_requests = all_requests.order_by(PermissionRequest.requested_at.desc()).all() owners_by_arg_by_perm = get_owners_by_grantable_permission(session) requests = [] for request in all_requests: owner_arg_list = get_owner_arg_list(session, request.permission, request.argument, owners_by_arg_by_perm) if group_ids.intersection([o.id for o, arg in owner_arg_list]): requests.append(request) total = len(requests) requests = requests[offset:limit] status_change_by_request_id = defaultdict(list) if not requests: comment_by_status_change_id = {} else: status_changes = session.query(PermissionRequestStatusChange).filter( PermissionRequestStatusChange.request_id.in_([r.id for r in requests]), ).all() for sc in status_changes: status_change_by_request_id[sc.request_id].append(sc) comments = session.query(Comment).filter( Comment.obj_type == OBJ_TYPES_IDX.index("PermissionRequestStatusChange"), Comment.obj_pk.in_([s.id for s in status_changes]), ).all() comment_by_status_change_id = {c.obj_pk: c for c in comments} return Requests(requests, status_change_by_request_id, comment_by_status_change_id), total
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.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.flush() if new_status == "actioned": # actually grant permission request.group.grant_permission(request.permission, request.argument) Counter.incr(session, "updates") # 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) # send notification if new_status == "actioned": subject = "Request for Permission Actioned" email_template = "permission_request_actioned" else: subject = "Request for Permission Cancelled" 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) owner_arg_list = get_owner_arg_list(session, permission, argument) 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, } # TODO: would be nicer if it told you which group you're an approver of # that's causing this notification mail_to = [] non_wildcard_owners = [(owner, arg) for owner, arg in owner_arg_list if arg != "*"] if any(non_wildcard_owners): # non-wildcard owners should get all the notifications mailto_owner_arg_list = non_wildcard_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'] send_email(session, set(mail_to), "Request for permission: {}".format(permission.name), "pending_permission_request", settings, email_context)