Exemple #1
0
    def approve_member_invitation(self,
                                  user_to_approve,
                                  api_key=None,
                                  ip_address=None,
                                  referrer=None):
        """
        Approve a member invite/join request and send an audit log entry
        """
        from sentry.models.auditlogentry import AuditLogEntryEvent
        from sentry.utils.audit import create_audit_entry_from_user

        self.approve_invite()
        self.save()

        if settings.SENTRY_ENABLE_INVITES:
            self.send_invite_email()
            member_invited.send_robust(
                member=self,
                user=user_to_approve,
                sender=self.approve_member_invitation,
                referrer=referrer,
            )

        create_audit_entry_from_user(
            user_to_approve,
            api_key,
            ip_address,
            organization_id=self.organization_id,
            target_object=self.id,
            data=self.get_audit_log_data(),
            event=AuditLogEntryEvent.MEMBER_INVITE if
            settings.SENTRY_ENABLE_INVITES else AuditLogEntryEvent.MEMBER_ADD,
        )
Exemple #2
0
    def post(self, request, organization):
        serializer = OrganizationMemberSerializer(
            data={
                "email": request.data.get("userName"),
                "role": roles.get(organization.default_role).id,
            },
            context={
                "organization": organization,
                "allowed_roles": [roles.get(organization.default_role)],
                "allow_existing_invite_request": True,
            },
        )

        if not serializer.is_valid():
            if "email" in serializer.errors and any(
                ("is already a member" in error)
                    for error in serializer.errors["email"]):
                # we include conflict logic in the serializer, check to see if that was
                # our error and if so, return a 409 so the scim IDP knows how to handle
                raise ConflictError(detail=SCIM_409_USER_EXISTS)
            return Response(serializer.errors, status=400)

        result = serializer.validated_data
        with transaction.atomic():
            member = OrganizationMember(
                organization=organization,
                email=result["email"],
                role=result["role"],
                inviter=request.user,
            )

            # TODO: are invite tokens needed for SAML orgs?
            if settings.SENTRY_ENABLE_INVITES:
                member.token = member.generate_token()
            member.save()

        self.create_audit_entry(
            request=request,
            organization_id=organization.id,
            target_object=member.id,
            data=member.get_audit_log_data(),
            event=AuditLogEntryEvent.MEMBER_INVITE if
            settings.SENTRY_ENABLE_INVITES else AuditLogEntryEvent.MEMBER_ADD,
        )

        if settings.SENTRY_ENABLE_INVITES and result.get("sendInvite"):
            member.send_invite_email()
            member_invited.send_robust(
                member=member,
                user=request.user,
                sender=self,
                referrer=request.data.get("referrer"),
            )

        context = serialize(
            member,
            serializer=_scim_member_serializer_with_expansion(organization),
        )
        return Response(context, status=201)
    def post(self, request, organization):
        """
        Add a Member to Organization
        ````````````````````````````

        Invite a member to the organization.

        :pparam string organization_slug: the slug of the organization the member will belong to
        :param string email: the email address to invite
        :param string role: the role of the new member
        :param array teams: the slugs of the teams the member should belong to.

        :auth: required
        """
        # TODO: If the member already exists, should this still update the role and team?
        # For now, it doesn't, but simply returns the existing object

        if not features.has('organizations:invite-members',
                            organization,
                            actor=request.user):
            return Response(
                {
                    'organization':
                    'Your organization is not allowed to invite members'
                },
                status=403)

        serializer = OrganizationMemberSerializer(data=request.DATA)

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.object

        _, allowed_roles = get_allowed_roles(request, organization)

        # ensure listed teams are real teams
        teams = list(
            Team.objects.filter(
                organization=organization,
                status=TeamStatus.VISIBLE,
                slug__in=result['teams'],
            ))

        if len(set(result['teams'])) != len(teams):
            return Response({'teams': 'Invalid team'}, 400)

        if not result['role'] in {r.id for r in allowed_roles}:
            return Response(
                {'role': 'You do not have permission to invite that role.'},
                403)

        # This is needed because `email` field is case sensitive, but from a user perspective,
        # Sentry treats email as case-insensitive ([email protected] equals [email protected]).

        existing = OrganizationMember.objects.filter(
            organization=organization,
            user__email__iexact=result['email'],
            user__is_active=True,
        ).exists()

        if existing:
            return Response(
                {'email': 'The user %s is already a member' % result['email']},
                409)

        om = OrganizationMember(organization=organization,
                                email=result['email'],
                                role=result['role'])

        if settings.SENTRY_ENABLE_INVITES:
            om.token = om.generate_token()

        try:
            with transaction.atomic():
                om.save()
        except IntegrityError:
            return Response(
                {'email': 'The user %s is already a member' % result['email']},
                409)

        lock = locks.get(u'org:member:{}'.format(om.id), duration=5)
        with TimedRetryPolicy(10)(lock.acquire):
            self.save_team_assignments(om, teams)

        if settings.SENTRY_ENABLE_INVITES:
            om.send_invite_email()
            member_invited.send_robust(member=om,
                                       user=request.user,
                                       sender=self,
                                       referrer=request.DATA.get('referrer'))

        self.create_audit_entry(
            request=request,
            organization_id=organization.id,
            target_object=om.id,
            data=om.get_audit_log_data(),
            event=AuditLogEntryEvent.MEMBER_INVITE if
            settings.SENTRY_ENABLE_INVITES else AuditLogEntryEvent.MEMBER_ADD,
        )

        return Response(serialize(om), status=201)
    def put(self, request, organization, member_id):
        """
        Update an invite request to Organization
        ````````````````````````````````````````

        Update and/or approve an invite request to an organization.

        :pparam string organization_slug: the slug of the organization the member will belong to
        :param string member_id: the member ID
        :param boolean approve: allows the member to be invited
        :param string role: the suggested role of the new member
        :param array teams: the suggested slugs of the teams the member should belong to.

        :auth: required
        """

        try:
            member = self._get_member(organization, member_id)
        except OrganizationMember.DoesNotExist:
            raise ResourceDoesNotExist

        serializer = OrganizationMemberSerializer(
            data=request.data,
            context={
                "organization": organization,
                "allowed_roles": roles.get_all()
            },
            partial=True,
        )

        if not serializer.is_valid():
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

        result = serializer.validated_data

        if result.get("role"):
            member.update(role=result["role"])

        if "teams" in result:
            save_team_assignments(member, result["teams"])

        if "approve" in request.data:
            _, allowed_roles = get_allowed_roles(request, organization)

            serializer = ApproveInviteRequestSerializer(
                data=request.data,
                context={
                    "request": request,
                    "organization": organization,
                    "member": member,
                    "allowed_roles": allowed_roles,
                },
            )

            if not serializer.is_valid():
                return Response(serializer.errors,
                                status=status.HTTP_400_BAD_REQUEST)

            result = serializer.validated_data

            if result.get("approve") and not member.invite_approved:
                member.approve_invite()
                member.save()

                if settings.SENTRY_ENABLE_INVITES:
                    member.send_invite_email()
                    member_invited.send_robust(
                        member=member,
                        user=request.user,
                        sender=self,
                        referrer=request.data.get("referrer"),
                    )

                self.create_audit_entry(
                    request=request,
                    organization_id=organization.id,
                    target_object=member.id,
                    data=member.get_audit_log_data(),
                    event=AuditLogEntryEvent.MEMBER_INVITE
                    if settings.SENTRY_ENABLE_INVITES else
                    AuditLogEntryEvent.MEMBER_ADD,
                )

        return Response(
            serialize(member,
                      serializer=OrganizationMemberWithTeamsSerializer()),
            status=status.HTTP_200_OK,
        )
    def post(self, request, organization):
        """
        Add a Member to Organization
        ````````````````````````````

        Invite a member to the organization.

        :pparam string organization_slug: the slug of the organization the member will belong to
        :param string email: the email address to invite
        :param string role: the role of the new member
        :param array teams: the slugs of the teams the member should belong to.

        :auth: required
        """
        if not features.has("organizations:invite-members",
                            organization,
                            actor=request.user):
            return Response(
                {
                    "organization":
                    "Your organization is not allowed to invite members"
                },
                status=403)

        _, allowed_roles = get_allowed_roles(request, organization)

        serializer = OrganizationMemberSerializer(
            data=request.data,
            context={
                "organization": organization,
                "allowed_roles": allowed_roles,
                "allow_existing_invite_request": True,
            },
        )

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.validated_data

        with transaction.atomic():
            # remove any invitation requests for this email before inviting
            OrganizationMember.objects.filter(
                Q(invite_status=InviteStatus.REQUESTED_TO_BE_INVITED.value)
                | Q(invite_status=InviteStatus.REQUESTED_TO_JOIN.value),
                email=result["email"],
                organization=organization,
            ).delete()

            om = OrganizationMember(
                organization=organization,
                email=result["email"],
                role=result["role"],
                inviter=request.user,
            )

            if settings.SENTRY_ENABLE_INVITES:
                om.token = om.generate_token()
            om.save()

        if result["teams"]:
            lock = locks.get(u"org:member:{}".format(om.id), duration=5)
            with TimedRetryPolicy(10)(lock.acquire):
                save_team_assignments(om, result["teams"])

        if settings.SENTRY_ENABLE_INVITES and result.get("sendInvite"):
            om.send_invite_email()
            member_invited.send_robust(member=om,
                                       user=request.user,
                                       sender=self,
                                       referrer=request.data.get("referrer"))

        self.create_audit_entry(
            request=request,
            organization_id=organization.id,
            target_object=om.id,
            data=om.get_audit_log_data(),
            event=AuditLogEntryEvent.MEMBER_INVITE if
            settings.SENTRY_ENABLE_INVITES else AuditLogEntryEvent.MEMBER_ADD,
        )

        return Response(serialize(om), status=201)
    def post(self, request, organization):
        """
        Add a Member to Organization
        ````````````````````````````

        Invite a member to the organization.

        :pparam string organization_slug: the slug of the organization the member will belong to
        :param string email: the email address to invite
        :param string role: the role of the new member
        :param array teams: the slugs of the teams the member should belong to.

        :auth: required
        """
        # TODO: If the member already exists, should this still update the role and team?
        # For now, it doesn't, but simply returns the existing object

        if not features.has('organizations:invite-members', organization, actor=request.user):
            return Response(
                {'organization': 'Your organization is not allowed to invite members'}, status=403)

        serializer = OrganizationMemberSerializer(data=request.DATA)

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.object

        _, allowed_roles = get_allowed_roles(request, organization)

        # ensure listed teams are real teams
        teams = list(Team.objects.filter(
            organization=organization,
            status=TeamStatus.VISIBLE,
            slug__in=result['teams'],
        ))

        if len(set(result['teams'])) != len(teams):
            return Response({'teams': 'Invalid team'}, 400)

        if not result['role'] in {r.id for r in allowed_roles}:
            return Response({'role': 'You do not have permission to invite that role.'}, 403)

        # This is needed because `email` field is case sensitive, but from a user perspective,
        # Sentry treats email as case-insensitive ([email protected] equals [email protected]).

        existing = OrganizationMember.objects.filter(
            organization=organization,
            user__email__iexact=result['email'],
            user__is_active=True,
        ).exists()

        if existing:
            return Response({'email': 'The user %s is already a member' % result['email']}, 409)

        om = OrganizationMember(
            organization=organization,
            email=result['email'],
            role=result['role'])

        if settings.SENTRY_ENABLE_INVITES:
            om.token = om.generate_token()

        try:
            with transaction.atomic():
                om.save()
        except IntegrityError:
            return Response({'email': 'The user %s is already a member' % result['email']}, 409)

        lock = locks.get(u'org:member:{}'.format(om.id), duration=5)
        with TimedRetryPolicy(10)(lock.acquire):
            self.save_team_assignments(om, teams)

        if settings.SENTRY_ENABLE_INVITES:
            om.send_invite_email()
            member_invited.send_robust(member=om, user=request.user, sender=self,
                                       referrer=request.DATA.get('referrer'))
        self.create_audit_entry(
            request=request,
            organization_id=organization.id,
            target_object=om.id,
            data=om.get_audit_log_data(),
            event=AuditLogEntryEvent.MEMBER_INVITE if settings.SENTRY_ENABLE_INVITES else AuditLogEntryEvent.MEMBER_ADD,
        )

        return Response(serialize(om), status=201)