def post(self, request, organization): # TODO: confirm mixed case emails get converted to lowercase 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, ) 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, ) # TODO: are invite tokens needed for SAML orgs? if settings.SENTRY_ENABLE_INVITES: member.token = member.generate_token() member.save() 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=OrganizationMemberSCIMSerializer()) return Response(context, status=201)
def test_token_generation_unicode_key(self): member = OrganizationMember(id=1, organization_id=1, email='*****@*****.**') with self.settings( SECRET_KEY= "\xfc]C\x8a\xd2\x93\x04\x00\x81\xeak\x94\x02H\x1d\xcc&P'q\x12\xa2\xc0\xf2v\x7f\xbb*lX" ): assert member.token == 'df41d9dfd4ba25d745321e654e15b5d0'
def test_token_expires_at_set_on_save(self): organization = self.create_organization() member = OrganizationMember(organization=organization, email="*****@*****.**") member.token = member.generate_token() member.save() expires_at = timezone.now() + timedelta(days=INVITE_DAYS_VALID) assert member.token_expires_at assert member.token_expires_at.date() == expires_at.date()
def sentry_update_organization_member(organization, user, role): model = sentry_find_organization_member(organization, user) existing = model is not None if not existing: model = OrganizationMember() model.user = user model.organization = organization model.role = role model.save() return model, existing
def test_send_invite_email(self): organization = self.create_organization() member = OrganizationMember(id=1, organization=organization, email='*****@*****.**') with self.options({'system.url-prefix': 'http://example.com'}), self.tasks(): member.send_invite_email() assert len(mail.outbox) == 1 msg = mail.outbox[0] assert msg.to == ['*****@*****.**']
def test_regenerate_token(self): organization = self.create_organization() member = OrganizationMember(organization=organization, email="*****@*****.**") assert member.token is None assert member.token_expires_at is None member.regenerate_token() assert member.token assert member.token_expires_at expires_at = timezone.now() + timedelta(days=INVITE_DAYS_VALID) assert member.token_expires_at.date() == expires_at.date()
def get(self, request: Request) -> Response: org = Organization(id=1, slug="petal", name="Petal") project = Project(id=1, slug="nodejs", name="Node.js", organization=org) user = User(name="Nisanthan") OrganizationMember(organization=org, user=user, role="admin") notification = AutoSyncNotification(project) return render_preview_email_for_notification(notification, user)
def get(self, request): org = Organization(id=1, slug="organization", name="sentry corp") member = OrganizationMember(id=1, organization=org, email="*****@*****.**") context = {"url": member.get_invite_link(), "organization": org} return MailPreview( html_template="sentry/emails/setup_2fa.html", text_template="sentry/emails/setup_2fa.txt", context=context, ).render(request)
def test_send_sso_link_email(self): organization = self.create_organization() member = OrganizationMember(id=1, organization=organization, email="*****@*****.**") with self.options({"system.url-prefix": "http://example.com"}), self.tasks(): member.send_invite_email() assert len(mail.outbox) == 1 msg = mail.outbox[0] assert msg.to == ["*****@*****.**"]
def test_token_expiration(self): organization = self.create_organization() member = OrganizationMember(organization=organization, email="*****@*****.**") member.token = member.generate_token() member.save() assert member.is_pending assert member.token_expired is False member.token_expires_at = timezone.now() - timedelta(minutes=1) assert member.token_expired
def test_set_user(self): organization = self.create_organization() member = OrganizationMember(organization=organization, email="*****@*****.**") member.token = member.generate_token() member.save() user = self.create_user(email="*****@*****.**") member.set_user(user) assert member.is_pending is False assert member.token_expires_at is None assert member.token is None assert member.email is None
def get(self, request: Request) -> Response: requester_name = request.GET.get("requester_name", "Requester") recipient_name = request.GET.get("recipient_name", "Recipient") org = Organization(id=1, slug="petal", name="Petal") project = Project(id=1, slug="nodejs", name="Node.js", organization=org) user = User(name=recipient_name) member = OrganizationMember(organization=org, user=user, role="admin") preview = MailPreviewAdapter( **get_codeowners_request_builder_args(project, member, requester_name) ) return render_to_response("sentry/debug/mail/preview.html", {"preview": preview})
def test_send_sso_link_email(self): organization = self.create_organization() member = OrganizationMember(id=1, organization=organization, email='*****@*****.**') with self.settings(SENTRY_URL_PREFIX='http://example.com'): member.send_invite_email() assert len(mail.outbox) == 1 msg = mail.outbox[0] assert msg.to == ['*****@*****.**']
def invitation(request): org = Organization(id=1, slug="example", name="Example") om = OrganizationMember(id=1, email="*****@*****.**", organization=org) return MailPreview( html_template="sentry/emails/member-invite.html", text_template="sentry/emails/member-invite.txt", context={ "email": "*****@*****.**", "organization": org, "url": absolute_uri( reverse("sentry-accept-invite", kwargs={"member_id": om.id, "token": om.token}) ), }, ).render(request)
def get(self, request): org = Organization( id=1, slug='organization', name='sentry corp', ) member = OrganizationMember( id=1, organization=org, email='*****@*****.**') context = { 'url': member.get_invite_link(), 'organization': org } return MailPreview( html_template='sentry/emails/setup_2fa.html', text_template='sentry/emails/setup_2fa.txt', context=context, ).render(request)
def invitation(request): org = Organization( id=1, slug='example', name='Example', ) om = OrganizationMember( id=1, email='*****@*****.**', organization=org, ) return MailPreview( html_template='sentry/emails/member-invite.html', text_template='sentry/emails/member-invite.txt', context={ 'email': '*****@*****.**', 'organization': org, 'url': absolute_uri(reverse('sentry-accept-invite', kwargs={ 'member_id': om.id, 'token': om.token, })), }, ).render(request)
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 post(self, request: Request, organization) -> Response: """ Create a new Organization Member via a SCIM Users POST Request. - `userName` should be set to the SAML field used for email, and active should be set to `true`. - Sentry's SCIM API doesn't currently support setting users to inactive, and the member will be deleted if inactive is set to `false`. - The API also does not support setting secondary emails. """ 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 test_legacy_token_generation(self): member = OrganizationMember(id=1, organization_id=1, email="*****@*****.**") with self.settings(SECRET_KEY="a"): assert member.legacy_token == "f3f2aa3e57f4b936dfd4f42c38db003e"
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)
configure() # Create the org, team and project if needed from sentry.models import Team, Project, ProjectKey, User, Organization, OrganizationMember user = User.objects.get(pk=1) name = 'AgoraVoting' name2 = 'AuthApi' if Organization.objects.filter(name=name).count() == 0: organization = Organization() organization.name = name organization.save() om = OrganizationMember() om.organization = organization om.role = 'owner' om.user = user om.save() team = Team() team.name = name team.organization = organization team.save() project = Project() project.team = team project.name = name2 project.organization = organization project.save()
def test_token_generation(self): member = OrganizationMember(id=1, organization_id=1, email='*****@*****.**') with self.settings(SECRET_KEY='a'): assert member.token == 'f3f2aa3e57f4b936dfd4f42c38db003e'