Exemple #1
0
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.base.constants.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:
        if can_approve_request(session,
                               request,
                               owner,
                               group_ids=group_ids,
                               owners_by_arg_by_perm=owners_by_arg_by_perm):
            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
Exemple #2
0
    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")
Exemple #3
0
    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")
Exemple #4
0
    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 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")
Exemple #6
0
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.base.constants.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
Exemple #7
0
def get_changes_by_request_id(session, request_id):
    status_changes = (session.query(PermissionRequestStatusChange).filter(
        PermissionRequestStatusChange.request_id == request_id).all())

    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 [(sc, comment_by_status_change_id[sc.id]) for sc in status_changes]
Exemple #8
0
def get_changes_by_request_id(session, request_id):
        status_changes = session.query(PermissionRequestStatusChange).filter(
            PermissionRequestStatusChange.request_id == request_id,
        ).all()

        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 [
            (sc, comment_by_status_change_id[sc.id]) for sc in status_changes
        ]
    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")
Exemple #10
0
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)
Exemple #11
0
def get_requests(
    session: Session,
    status: str,
    limit: int,
    offset: int,
    owner: Optional[User] = None,
    requester: Optional[User] = None,
    owners_by_arg_by_perm: Optional[Dict[object, Dict[str,
                                                      List[Group]]]] = None,
) -> Tuple[Requests, int]:
    """Load requests using the given filters.

    Args:
        session: Database session
        status: If not None, filter by particular status
        limit: how many results to return
        offset: the offset into the result set that should be applied
        owner: If not None, filter by requests that the owner can action
        requester: If not None, filter by requests that the requester made
        owners_by_arg_by_perm: List of groups that can grant a given permission, argument pair in
            the format of
            {perm_name: {argument: [group1, group2, ...], ...}, ...}
            This is for convenience/caching if the value has already been fetched.

    Returns:
        2-tuple of (Requests, total) where total is total result size and Requests is the
        data transfer object with requests and associated comments/changes.
    """
    # get all requests
    all_requests = session.query(PermissionRequest)
    if status:
        all_requests = all_requests.filter(PermissionRequest.status == status)
    if requester:
        all_requests = all_requests.filter(
            PermissionRequest.requester_id == requester.id)

    all_requests = all_requests.order_by(
        PermissionRequest.requested_at.desc()).all()

    if owners_by_arg_by_perm is None:
        owners_by_arg_by_perm = get_owners_by_grantable_permission(session)

    if owner:
        group_ids = {g.id for g, _ in get_groups_by_user(session, owner)}
        requests = [
            request for request in all_requests if can_approve_request(
                session,
                request,
                owner,
                group_ids=group_ids,
                owners_by_arg_by_perm=owners_by_arg_by_perm,
            )
        ]
    else:
        requests = all_requests

    total = len(requests)
    requests = requests[offset:limit]

    status_change_by_request_id: Dict[
        int, List[PermissionRequestStatusChange]] = defaultdict(list)
    if not requests:
        comment_by_status_change_id: Dict[int, Comment] = {}
    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)
Exemple #12
0
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
Exemple #13
0
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)
Exemple #14
0
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.
        grouper.audit.UserNotAuditor if 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 = 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
Exemple #15
0
    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")
Exemple #16
0
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
Exemple #17
0
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)
Exemple #18
0
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)
Exemple #19
0
def get_requests(
    session, status, limit, offset, owner=None, requester=None, owners_by_arg_by_perm=None
):
    """Load requests using the given filters.

    Args:
        session(sqlalchemy.orm.session.Session): database session
        status(models.base.constants.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
        owner(models.User): if not None, filter by requests that the owner
            can action
        requester(models.User): if not None, filter by requests that the
            requester made
        owners_by_arg_by_perm(Dict): list of groups that can grant a given
            permission, argument pair in the format of
            {perm_name: {argument: [group1, group2, ...], ...}, ...}
            This is for convenience/caching if the value has already been fetched.

    Returns:
        2-tuple of (Requests, total) where total is total result size and
        Requests is the namedtuple with requests and associated
        comments/changes.
    """
    # get all requests
    all_requests = session.query(PermissionRequest)
    if status:
        all_requests = all_requests.filter(PermissionRequest.status == status)
    if requester:
        all_requests = all_requests.filter(PermissionRequest.requester_id == requester.id)

    all_requests = all_requests.order_by(PermissionRequest.requested_at.desc()).all()

    if owners_by_arg_by_perm is None:
        owners_by_arg_by_perm = get_owners_by_grantable_permission(session)

    if owner:
        group_ids = {g.id for g, _ in get_groups_by_user(session, owner)}
        requests = [
            request
            for request in all_requests
            if can_approve_request(
                session,
                request,
                owner,
                group_ids=group_ids,
                owners_by_arg_by_perm=owners_by_arg_by_perm,
            )
        ]
    else:
        requests = all_requests

    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)