def test_by_organization(self): organization = Organization(id=1) for n in range(5): assert not ratelimits.for_organization_member_invite( organization, f"{randint(0, 1000000)}@example.com", config=RELAXED_CONFIG) assert ratelimits.for_organization_member_invite( organization, "*****@*****.**", config=RELAXED_CONFIG)
def test_by_email(self): organization = Organization(id=1) email = "*****@*****.**" for n in range(2): assert not ratelimits.for_organization_member_invite( organization, email, config=RELAXED_CONFIG) assert ratelimits.for_organization_member_invite(organization, email, config=RELAXED_CONFIG)
def test_by_user(self): user = User(email="*****@*****.**") for n in range(5): assert not ratelimits.for_organization_member_invite( Organization(id=randint(0, 100000)), f"{randint(0, 1000000)}@example.com", user=user, config=RELAXED_CONFIG, ) assert ratelimits.for_organization_member_invite( Organization(id=1), "*****@*****.**", user=user, config=RELAXED_CONFIG)
def test_by_api_token(self): token = ApiToken(id=1) for n in range(5): assert not ratelimits.for_organization_member_invite( Organization(id=randint(0, 100000)), f"{randint(0, 1000000)}@example.com", auth=token, config=RELAXED_CONFIG, ) assert ratelimits.for_organization_member_invite( Organization(id=1), "*****@*****.**", auth=token, config=RELAXED_CONFIG)
def put(self, request: Request, organization, member_id) -> Response: try: om = self._get_member(request, organization, member_id) except OrganizationMember.DoesNotExist: raise ResourceDoesNotExist serializer = OrganizationMemberSerializer(data=request.data, partial=True) if not serializer.is_valid(): return Response(status=400) try: auth_provider = AuthProvider.objects.get(organization=organization) auth_provider = auth_provider.get_provider() except AuthProvider.DoesNotExist: auth_provider = None allowed_roles = None result = serializer.validated_data # XXX(dcramer): if/when this expands beyond reinvite we need to check # access level if result.get("reinvite"): if om.is_pending: if ratelimits.for_organization_member_invite( organization=organization, email=om.email, user=request.user, auth=request.auth, ): metrics.incr( "member-invite.attempt", instance="rate_limited", skip_internal=True, sample_rate=1.0, ) return Response({"detail": ERR_RATE_LIMITED}, status=429) if result.get("regenerate"): if request.access.has_scope("member:admin"): om.regenerate_token() om.save() else: return Response({"detail": ERR_INSUFFICIENT_SCOPE}, status=400) if om.token_expired: return Response({"detail": ERR_EXPIRED}, status=400) om.send_invite_email() elif auth_provider and not getattr(om.flags, "sso:linked"): om.send_sso_link_email(request.user, auth_provider) else: # TODO(dcramer): proper error message return Response({"detail": ERR_UNINVITABLE}, status=400) if "teams" in result: # dupe code from member_index # 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"}, status=400) with transaction.atomic(): # teams may be empty OrganizationMemberTeam.objects.filter( organizationmember=om).delete() OrganizationMemberTeam.objects.bulk_create([ OrganizationMemberTeam(team=team, organizationmember=om) for team in teams ]) if result.get("role"): _, allowed_roles = get_allowed_roles(request, organization) allowed_role_ids = {r.id for r in allowed_roles} # A user cannot promote others above themselves if result["role"] not in allowed_role_ids: return Response( { "role": "You do not have permission to assign the given role." }, status=403) # A user cannot demote a superior if om.role not in allowed_role_ids: return Response( { "role": "You do not have permission to assign a role to the given user." }, status=403, ) if om.user == request.user and (result["role"] != om.role): return Response( {"detail": "You cannot make changes to your own role."}, status=400) om.update(role=result["role"]) self.create_audit_entry( request=request, organization=organization, target_object=om.id, target_user=om.user, event=AuditLogEntryEvent.MEMBER_EDIT, data=om.get_audit_log_data(), ) context = self._serialize_member(om, request, allowed_roles) return Response(context)
def post(self, request: Request, organization) -> Response: """ 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 if ratelimits.for_organization_member_invite( organization=organization, email=result["email"], user=request.user, auth=request.auth, ): metrics.incr( "member-invite.attempt", instance="rate_limited", skip_internal=True, sample_rate=1.0, ) return Response({"detail": ERR_RATE_LIMITED}, status=429) 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(f"org:member:{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)