def map_params_to_state(self, params): # decode the signed params and add them to whatever params we have params = params.copy() signed_params = params["signed_params"] del params["signed_params"] params.update(unsign(signed_params.encode("ascii", errors="ignore"))) return params
def handle(self, request: Request, signed_params: str) -> Response: try: params = unsign(signed_params) except (SignatureExpired, BadSignature): return render_to_response( "sentry/integrations/slack/expired-link.html", request=request, ) organization, integration, idp = get_identity_or_404( ExternalProviders.SLACK, request.user, integration_id=params["integration_id"], ) if request.method != "POST": return render_to_response( "sentry/auth-unlink-identity.html", request=request, context={"organization": organization, "provider": integration.get_provider()}, ) try: Identity.objects.filter(idp=idp, external_id=params["slack_id"]).delete() except IntegrityError as e: logger.error("slack.unlink.integrity-error", extra=e) raise Http404 send_slack_response(integration, SUCCESS_UNLINKED_MESSAGE, params, command="unlink") return render_to_response( "sentry/integrations/slack/unlinked.html", request=request, context={"channel_id": params["channel_id"], "team_id": integration.external_id}, )
def map_params_to_state(self, params): # decode the signed params and add them to whatever params we have params = params.copy() signed_params = params["signed_params"] del params["signed_params"] params.update(unsign(signed_params, max_age=INSTALL_EXPIRATION_TIME,)) return params
def handle(self, request: Request, signed_params: str) -> Response: try: params = unsign(signed_params) except (SignatureExpired, BadSignature): return render_to_response( "sentry/integrations/slack/expired-link.html", request=request, ) organization, integration, idp = get_identity_or_404( ExternalProviders.SLACK, request.user, integration_id=params["integration_id"], organization_id=params["organization_id"], ) if request.method != "POST": return render_to_response( "sentry/auth-link-identity.html", request=request, context={ "organization": organization, "provider": integration.get_provider() }, ) # TODO(epurkhiser): We could do some fancy slack querying here to # render a nice linking page with info about the user their linking. # Link the user with the identity. Handle the case where the user is linked to a # different identity or the identity is linked to a different user. defaults = { "status": IdentityStatus.VALID, "date_verified": timezone.now() } try: identity, created = Identity.objects.get_or_create( idp=idp, user=request.user, external_id=params["slack_id"], defaults=defaults) if not created: identity.update(**defaults) except IntegrityError: Identity.objects.reattach(idp, params["slack_id"], request.user, defaults) send_slack_response(integration, SUCCESS_LINKED_MESSAGE, params, command="link") return render_to_response( "sentry/integrations/slack/linked.html", request=request, context={ "channel_id": params["channel_id"], "team_id": integration.external_id }, )
def handle(self, request, signed_params): params = unsign(signed_params) organization, integration, idp = get_identity( request.user, params["organization_id"], params["integration_id"] ) if request.method != "POST": return render_to_response( "sentry/auth-link-identity.html", request=request, context={"organization": organization, "provider": integration.get_provider()}, ) defaults = {"status": IdentityStatus.VALID, "date_verified": timezone.now()} try: identity, created = Identity.objects.get_or_create( idp=idp, user=request.user, external_id=params["teams_user_id"], defaults=defaults ) if not created: identity.update(**defaults) except IntegrityError: Identity.reattach(idp, params["teams_user_id"], request.user, defaults) card = build_linked_card() client = MsTeamsClient(integration) user_conversation_id = client.get_user_conversation_id( params["teams_user_id"], params["tenant_id"] ) client.send_card(user_conversation_id, card) return render_to_response( "sentry/msteams-linked.html", request=request, context={"team_id": params["team_id"]} )
def handle(self, request, signed_params): params = unsign(signed_params) if request.method != "POST": return render_to_response( "sentry/integrations/msteams-unlink-identity.html", request=request, context={}, ) # find the identities linked to this team user and sentry user identity_list = Identity.objects.filter( external_id=params["teams_user_id"], user=request.user) # if no identities, tell the user that if not identity_list: return render_to_response( "sentry/integrations/msteams-no-identity.html", request=request, context={}, ) # otherwise, delete the identities, send message to the user, and render a success screen identity_list.delete() client = get_preinstall_client(params["service_url"]) card = build_unlinked_card() client.send_card(params["conversation_id"], card) return render_to_response( "sentry/integrations/msteams-unlinked.html", request=request, context={}, )
def handle(self, request: Request, signed_params: str) -> Response: params = unsign(signed_params) organization, integration, idp = get_identity( request.user, params["organization_id"], params["integration_id"]) channel_name = params["channel_name"] channel_id = params["channel_id"] external_teams = ExternalActor.objects.filter( organization=organization, integration=integration, provider=ExternalProviders.SLACK.value, external_name=channel_name, external_id=channel_id, ) if len(external_teams) == 0: logger.error("slack.team.unlink.no_external_teams") raise Http404 teams = Team.objects.filter(actor__in=[ external_team.actor for external_team in external_teams ]) if request.method != "POST": return render_to_response( "sentry/integrations/slack-unlink-team.html", request=request, context={ "team": teams[0], "channel_name": channel_name, "provider": integration.get_provider(), }, ) try: idp = IdentityProvider.objects.get( type="slack", external_id=integration.external_id) except IdentityProvider.DoesNotExist: logger.error("slack.action.invalid-team-id", extra={"slack_id": integration.external_id}) return render_error_page(request, body_text="HTTP 403: Invalid team ID") if not Identity.objects.filter( idp=idp, external_id=params["slack_id"]).exists(): return render_error_page( request, body_text="HTTP 403: User identity does not exist") for external_team in external_teams: external_team.delete() for team in teams: NotificationSetting.objects.remove_for_team( team, ExternalProviders.SLACK) return send_confirmation( integration, channel_id, SUCCESS_UNLINKED_TITLE, SUCCESS_UNLINKED_MESSAGE.format(team=team.slug), "sentry/integrations/slack-unlinked-team.html", request, )
def handle(self, request: Request, signed_params: str) -> Response: try: params = unsign(signed_params) except (SignatureExpired, BadSignature): return render_to_response( "sentry/integrations/slack/expired-link.html", request=request, ) organization, integration, idp = get_identity_or_404( ExternalProviders.SLACK, request.user, integration_id=params["integration_id"], organization_id=params["organization_id"], ) channel_name = params["channel_name"] channel_id = params["channel_id"] external_teams = ExternalActor.objects.filter( organization=organization, integration=integration, provider=ExternalProviders.SLACK.value, external_name=channel_name, external_id=channel_id, ) if len(external_teams) == 0: return render_error_page(request, body_text="HTTP 404: Team not found") team = external_teams[0].actor.resolve() if request.method != "POST": return render_to_response( "sentry/integrations/slack/unlink-team.html", request=request, context={ "team": team, "channel_name": channel_name, "provider": integration.get_provider(), }, ) if not Identity.objects.filter( idp=idp, external_id=params["slack_id"]).exists(): return render_error_page( request, body_text="HTTP 403: User identity does not exist") # Someone may have accidentally added multiple teams so unlink them all. for external_team in external_teams: external_team.delete() return send_confirmation( integration, channel_id, SUCCESS_UNLINKED_TITLE, SUCCESS_UNLINKED_MESSAGE.format(team=team.slug), "sentry/integrations/slack/unlinked-team.html", request, )
def handle(self, request, signed_params): params = unsign(signed_params.encode("ascii", errors="ignore")) organization, integration, idp = get_identity( request.user, params["organization_id"], params["integration_id"]) if request.method != "POST": return render_to_response( "sentry/auth-unlink-identity.html", request=request, context={ "organization": organization, "provider": integration.get_provider() }, ) # Delete the wrong slack identity. try: identity = Identity.objects.get(idp=idp, external_id=params["slack_id"]) identity.delete() except IntegrityError as e: logger.error("slack.unlink.integrity-error", extra=e) raise Http404 payload = { "replace_original": False, "response_type": "ephemeral", "text": "Your Slack identity has been unlinked from your Sentry account.", } client = SlackClient() try: client.post(params["response_url"], data=payload, json=True) except ApiError as e: message = six.text_type(e) # If the user took their time to link their slack account, we may no # longer be able to respond, and we're not guaranteed able to post into # the channel. Ignore Expired url errors. # # XXX(epurkhiser): Yes the error string has a space in it. if message != "Expired url": logger.error("slack.unlink-notify.response-error", extra={"error": message}) return render_to_response( "sentry/slack-unlinked.html", request=request, context={ "channel_id": params["channel_id"], "team_id": integration.external_id }, )
def handle(self, request, signed_params): params = unsign(signed_params) organization, integration, idp = get_identity( request.user, params["organization_id"], params["integration_id"] ) if request.method != "POST": return render_to_response( "sentry/auth-link-identity.html", request=request, context={"organization": organization, "provider": integration.get_provider()}, ) # TODO(epurkhiser): We could do some fancy slack querying here to # render a nice linking page with info about the user their linking. # Link the user with the identity. Handle the case where the user is linked to a # different identity or the identity is linked to a different user. defaults = {"status": IdentityStatus.VALID, "date_verified": timezone.now()} try: identity, created = Identity.objects.get_or_create( idp=idp, user=request.user, external_id=params["slack_id"], defaults=defaults ) if not created: identity.update(**defaults) except IntegrityError: Identity.reattach(idp, params["slack_id"], request.user, defaults) payload = { "replace_original": False, "response_type": "ephemeral", "text": "Your Slack identity has been linked to your Sentry account. You're good to go!", } client = SlackClient() try: client.post(params["response_url"], data=payload, json=True) except ApiError as e: message = six.text_type(e) # If the user took their time to link their slack account, we may no # longer be able to respond, and we're not guaranteed able to post into # the channel. Ignore Expired url errors. # # XXX(epurkhiser): Yes the error string has a space in it. if message != "Expired url": logger.error("slack.link-notify.response-error", extra={"error": message}) return render_to_response( "sentry/slack-linked.html", request=request, context={"channel_id": params["channel_id"], "team_id": integration.external_id}, )
def handle(self, request: Request, signed_params: str) -> Response: try: params = unsign(signed_params) except (SignatureExpired, BadSignature): return render_to_response( "sentry/integrations/slack/expired-link.html", request=request, ) organization, integration, idp = get_identity_or_404( ExternalProviders.SLACK, request.user, integration_id=params["integration_id"], ) if request.method != "POST": return render_to_response( "sentry/auth-link-identity.html", request=request, context={ "organization": organization, "provider": integration.get_provider() }, ) Identity.objects.link_identity(user=request.user, idp=idp, external_id=params["slack_id"]) send_slack_response(integration, SUCCESS_LINKED_MESSAGE, params, command="link") if not NotificationSetting.objects.has_any_provider_settings( request.user, ExternalProviders.SLACK): IntegrationNudgeNotification(organization, request.user, ExternalProviders.SLACK).send() # TODO(epurkhiser): We could do some fancy slack querying here to # render a nice linking page with info about the user their linking. return render_to_response( "sentry/integrations/slack/linked.html", request=request, context={ "channel_id": params["channel_id"], "team_id": integration.external_id }, )
def get_validated_data(self, data, user): try: data = unsign(force_str(data)) except SignatureExpired: raise InvalidPayload('Project transfer link has expired.') except BadSignature: raise InvalidPayload('Could not approve transfer, please make sure link is valid.') if data['user_id'] != user.id: raise InvalidPayload('Invalid permissions') try: project = Project.objects.get( id=data['project_id'], organization_id=data['from_organization_id'], ) except Project.DoesNotExist: raise InvalidPayload('Project no longer exists') return data, project
def get_validated_data(self, data, user): try: data = unsign(force_str(data)) except SignatureExpired: raise InvalidPayload("Project transfer link has expired.") except BadSignature: raise InvalidPayload("Could not approve transfer, please make sure link is valid.") if data["user_id"] != user.id: raise InvalidPayload("Invalid permissions") try: project = Project.objects.get( id=data["project_id"], organization_id=data["from_organization_id"] ) except Project.DoesNotExist: raise InvalidPayload("Project no longer exists") return data, project
def handle(self, request: Request, signed_params) -> Response: try: params = unsign(signed_params) except (SignatureExpired, BadSignature): return render_to_response( "sentry/integrations/msteams/expired-link.html", request=request, ) organization, integration, idp = get_identity_or_404( ExternalProviders.MSTEAMS, request.user, integration_id=params["integration_id"], organization_id=params["organization_id"], ) if request.method != "POST": return render_to_response( "sentry/auth-link-identity.html", request=request, context={ "organization": organization, "provider": integration.get_provider() }, ) Identity.objects.link_identity(user=request.user, idp=idp, external_id=params["teams_user_id"]) card = build_linked_card() client = MsTeamsClient(integration) user_conversation_id = client.get_user_conversation_id( params["teams_user_id"], params["tenant_id"]) client.send_card(user_conversation_id, card) return render_to_response( "sentry/integrations/msteams/linked.html", request=request, context={"team_id": params["team_id"]}, )
def test_basic_flow(self): teams_user_id = "my-teams-user-id" Identity.objects.create(user=self.user1, idp=self.idp, external_id=teams_user_id, status=IdentityStatus.VALID) unlink_url = build_unlinking_url( self.conversation_id, "https://smba.trafficmanager.net/amer", teams_user_id) signed_params = unlink_url.split("/")[-2] params = unsign(signed_params) assert params == { "conversation_id": self.conversation_id, "service_url": "https://smba.trafficmanager.net/amer", "teams_user_id": teams_user_id, } resp = self.client.get(unlink_url) assert resp.status_code == 200 self.assertTemplateUsed( resp, "sentry/integrations/msteams-unlink-identity.html") # Unlink identity of user resp = self.client.post(unlink_url) assert resp.status_code == 200 self.assertTemplateUsed(resp, "sentry/integrations/msteams-unlinked.html") identity = Identity.objects.filter(external_id=teams_user_id, user=self.user1) assert len(identity) == 0 assert ( "Your Microsoft Teams identity has been unlinked to your Sentry account." in responses.calls[1].request.body.decode("utf-8")) assert len(responses.calls) == 2
def handle(self, request: Request, signed_params: str) -> Response: params = unsign(signed_params) organization, integration, idp = get_identity( request.user, params["organization_id"], params["integration_id"]) if request.method != "POST": return render_to_response( "sentry/auth-unlink-identity.html", request=request, context={ "organization": organization, "provider": integration.get_provider() }, ) # Delete the wrong slack identity. try: identity = Identity.objects.get(idp=idp, external_id=params["slack_id"]) identity.delete() except IntegrityError as e: logger.error("slack.unlink.integrity-error", extra=e) raise Http404 send_slack_response(integration, SUCCESS_UNLINKED_MESSAGE, params, command="unlink") return render_to_response( "sentry/integrations/slack-unlinked.html", request=request, context={ "channel_id": params["channel_id"], "team_id": integration.external_id }, )
def handle(self, request, *args, **kwargs): try: d = request.GET['data'] except KeyError: raise Http404 try: data = unsign(force_str(d)) except BadSignature: messages.add_message( request, messages.ERROR, _(u'Could not approve transfer, please make sure link is valid.') ) return HttpResponseRedirect( reverse('sentry') ) except SignatureExpired: messages.add_message( request, messages.ERROR, _(u'Project transfer link has expired!') ) return HttpResponseRedirect( reverse('sentry') ) project_id = data['project_id'] user_id = data['user_id'] transaction_id = data['transaction_id'] from_organization_id = data['from_organization_id'] if user_id != request.user.id: messages.add_message( request, messages.ERROR, _(u'Invalid permissions!') ) return HttpResponseRedirect( reverse('sentry') ) # check if user is still an owner if not OrganizationMember.objects.filter( role=roles.get_top_dog().id, user__is_active=True, user_id=user_id, ).exists(): return HttpResponseRedirect( reverse('sentry') ) try: project = Project.objects.get(id=project_id, organization_id=from_organization_id) except Project.DoesNotExist: messages.add_message( request, messages.ERROR, _(u'Project no longer exists') ) return HttpResponseRedirect( reverse('sentry') ) form = self.get_form(request) if form.is_valid(): # transfer the project team_id = form.cleaned_data.get('team') new_team = Team.objects.get(id=team_id) project.transfer_to(new_team) self.create_audit_entry( request=request, organization=project.organization, target_object=project_id, event=AuditLogEntryEvent.PROJECT_ACCEPT_TRANSFER, data=project.get_audit_log_data(), transaction_id=transaction_id, ) return HttpResponseRedirect( reverse('sentry-organization-home', args=[new_team.organization.slug]) ) context = { 'project': project, 'form': form, } return self.respond('sentry/projects/accept_project_transfer.html', context)
def handle(self, request, signed_params): params = unsign(signed_params.encode('ascii', errors='ignore')) try: organization = Organization.objects.get( id__in=request.user.get_orgs(), id=params['organization_id'], ) except Organization.DoesNotExist: raise Http404 try: integration = Integration.objects.get( id=params['integration_id'], organizations=organization, ) except Integration.DoesNotExist: raise Http404 try: idp = IdentityProvider.objects.get( Q(external_id=integration.external_id) | Q(external_id=None), type='slack', organization=organization, ) except Integration.DoesNotExist: raise Http404 if request.method != 'POST': return render_to_response('sentry/auth-link-identity.html', request=request, context={ 'organization': organization, 'provider': integration.get_provider(), }) # TODO(epurkhiser): We could do some fancy slack querying here to # render a nice linking page with info about the user their linking. identity, created = Identity.objects.get_or_create( user=request.user, idp=idp, defaults={ 'external_id': params['slack_id'], 'status': IdentityStatus.VALID, }, ) if not created: # TODO(epurkhiser): In this case we probably want to prompt and # warn them that they had a previous identity linked to slack. identity.update( external_id=params['slack_id'], status=IdentityStatus.VALID ) payload = { 'replace_original': False, 'response_type': 'ephemeral', 'text': "Your Slack identity has been linked to your Sentry account. You're good to go!" } session = http.build_session() req = session.post(params['response_url'], json=payload) resp = req.json() # If the user took their time to link their slack account, we may no # longer be able to respond, and we're not guaranteed able to post into # the channel. Ignore Expired url errors. # # XXX(epurkhiser): Yes the error string has a space in it. if not resp.get('ok') and resp.get('error') != 'Expired url': logger.error('slack.link-notify.response-error', extra={'response': resp}) return render_to_response('sentry/slack-linked.html', request=request, context={ 'channel_id': params['channel_id'], 'team_id': integration.external_id, })
def handle(self, request, signed_params): params = unsign(signed_params.encode("ascii", errors="ignore")) try: organization = Organization.objects.get( id__in=request.user.get_orgs(), id=params["organization_id"]) except Organization.DoesNotExist: raise Http404 try: integration = Integration.objects.get(id=params["integration_id"], organizations=organization) except Integration.DoesNotExist: raise Http404 try: idp = IdentityProvider.objects.get( external_id=integration.external_id, type="slack") except IdentityProvider.DoesNotExist: raise Http404 if request.method != "POST": return render_to_response( "sentry/auth-link-identity.html", request=request, context={ "organization": organization, "provider": integration.get_provider() }, ) # TODO(epurkhiser): We could do some fancy slack querying here to # render a nice linking page with info about the user their linking. # Link the user with the identity. Handle the case where the user is linked to a # different identity or the identity is linked to a different user. defaults = { "status": IdentityStatus.VALID, "date_verified": timezone.now() } try: identity, created = Identity.objects.get_or_create( idp=idp, user=request.user, external_id=params["slack_id"], defaults=defaults) if not created: identity.update(**defaults) except IntegrityError: Identity.reattach(idp, params["slack_id"], request.user, defaults) payload = { "replace_original": False, "response_type": "ephemeral", "text": "Your Slack identity has been linked to your Sentry account. You're good to go!", } session = http.build_session() req = session.post(params["response_url"], json=payload) status_code = req.status_code resp = req.json() # If the user took their time to link their slack account, we may no # longer be able to respond, and we're not guaranteed able to post into # the channel. Ignore Expired url errors. # # XXX(epurkhiser): Yes the error string has a space in it. if not resp.get("ok") and resp.get("error") != "Expired url": logger.error("slack.link-notify.response-error", extra={"response": resp}) track_response_code(status_code, resp.get("ok")) return render_to_response( "sentry/slack-linked.html", request=request, context={ "channel_id": params["channel_id"], "team_id": integration.external_id }, )
def handle(self, request: Request, signed_params: str) -> HttpResponse: if request.method not in ALLOWED_METHODS: return render_error_page(request, body_text="HTTP 405: Method not allowed") try: params = unsign(signed_params) except (SignatureExpired, BadSignature): return render_to_response( "sentry/integrations/slack/expired-link.html", request=request, ) integration = Integration.objects.get(id=params["integration_id"]) organization_memberships = OrganizationMember.objects.get_for_integration( integration, request.user) # Filter to organizations where we have sufficient role. organizations = [ organization_membership.organization for organization_membership in organization_memberships if is_valid_role(organization_membership) ] teams_by_id = { team.id: team for organization in organizations for team in Team.objects.get_for_user(organization, request.user) } channel_name = params["channel_name"] channel_id = params["channel_id"] form = SelectTeamForm(list(teams_by_id.values()), request.POST or None) if request.method == "GET": return self.respond( "sentry/integrations/slack/link-team.html", { "form": form, "teams": teams_by_id.values(), "channel_name": channel_name, "provider": integration.get_provider(), }, ) if not form.is_valid(): return render_error_page(request, body_text="HTTP 400: Bad request") team_id = int(form.cleaned_data["team"]) team = teams_by_id.get(team_id) if not team: return render_error_page(request, body_text="HTTP 404: Team does not exist") try: idp = IdentityProvider.objects.get( type="slack", external_id=integration.external_id) except IdentityProvider.DoesNotExist: logger.error("slack.action.invalid-team-id", extra={"slack_id": integration.external_id}) return render_error_page(request, body_text="HTTP 403: Invalid team ID") if not Identity.objects.filter( idp=idp, external_id=params["slack_id"]).exists(): return render_error_page( request, body_text="HTTP 403: User identity does not exist") install = integration.get_installation(team.organization.id) external_team, created = ExternalActor.objects.get_or_create( actor_id=team.actor_id, organization=team.organization, integration=integration, provider=ExternalProviders.SLACK.value, defaults=dict( external_name=channel_name, external_id=channel_id, ), ) analytics.record( "integrations.identity_linked", provider="slack", actor_id=team.actor_id, actor_type="team", ) if not created: message = ALREADY_LINKED_MESSAGE.format(slug=team.slug) install.send_message(channel_id=channel_id, message=message) return render_to_response( "sentry/integrations/slack/post-linked-team.html", request=request, context={ "heading_text": ALREADY_LINKED_TITLE, "body_text": message, "channel_id": channel_id, "team_id": integration.external_id, }, ) # Turn on notifications for all of a team's projects. NotificationSetting.objects.update_settings( ExternalProviders.SLACK, NotificationSettingTypes.ISSUE_ALERTS, NotificationSettingOptionValues.ALWAYS, team=team, ) message = SUCCESS_LINKED_MESSAGE.format(slug=team.slug, channel_name=channel_name) install.send_message(channel_id=channel_id, message=message) return render_to_response( "sentry/integrations/slack/post-linked-team.html", request=request, context={ "heading_text": SUCCESS_LINKED_TITLE, "body_text": message, "channel_id": channel_id, "team_id": integration.external_id, }, )
def handle(self, request, *args, **kwargs): try: d = request.GET['data'] except KeyError: raise Http404 try: data = unsign(force_str(d)) except BadSignature: messages.add_message( request, messages.ERROR, _(u'Could not approve transfer, please make sure link is valid.' )) return HttpResponseRedirect(reverse('sentry')) except SignatureExpired: messages.add_message(request, messages.ERROR, _(u'Project transfer link has expired!')) return HttpResponseRedirect(reverse('sentry')) project_id = data['project_id'] user_id = data['user_id'] transaction_id = data['transaction_id'] from_organization_id = data['from_organization_id'] if user_id != request.user.id: messages.add_message(request, messages.ERROR, _(u'Invalid permissions!')) return HttpResponseRedirect(reverse('sentry')) # check if user is still an owner if not OrganizationMember.objects.filter( role=roles.get_top_dog().id, user__is_active=True, user_id=user_id, ).exists(): return HttpResponseRedirect(reverse('sentry')) try: project = Project.objects.get(id=project_id, organization_id=from_organization_id) except Project.DoesNotExist: messages.add_message(request, messages.ERROR, _(u'Project no longer exists')) return HttpResponseRedirect(reverse('sentry')) form = self.get_form(request) if form.is_valid(): # transfer the project team_id = form.cleaned_data.get('team') new_team = Team.objects.get(id=team_id) project.transfer_to(new_team) self.create_audit_entry( request=request, organization=project.organization, target_object=project_id, event=AuditLogEntryEvent.PROJECT_ACCEPT_TRANSFER, data=project.get_audit_log_data(), transaction_id=transaction_id, ) return HttpResponseRedirect( reverse('sentry-organization-home', args=[new_team.organization.slug])) context = { 'project': project, 'form': form, } return self.respond('sentry/projects/accept_project_transfer.html', context)
def handle(self, request, signed_params): params = unsign(signed_params.encode('ascii', errors='ignore')) try: organization = Organization.objects.get( id__in=request.user.get_orgs(), id=params['organization_id'], ) except Organization.DoesNotExist: raise Http404 try: integration = Integration.objects.get( id=params['integration_id'], organizations=organization, ) except Integration.DoesNotExist: raise Http404 try: idp = IdentityProvider.objects.get( type='slack', organization=organization, ) except Integration.DoesNotExist: raise Http404 if request.method != 'POST': return render_to_response('sentry/auth-link-identity.html', request=request, context={ 'organization': organization, 'provider': integration.get_provider(), }) # TODO(epurkhiser): We could do some fancy slack querying here to # render a nice linking page with info about the user their linking. Identity.objects.get_or_create( external_id=params['slack_id'], user=request.user, idp=idp, status=IdentityStatus.VALID, ) payload = { 'replace_original': False, 'response_type': 'ephemeral', 'text': "Your Slack identity has been linked to your Sentry account. You're good to go!" } session = http.build_session() req = session.post(params['response_url'], json=payload) resp = req.json() if not resp.get('ok'): logger.error('slack.link-notify.response-error', extra={ 'error': resp.get('error'), }) return render_to_response('sentry/slack-linked.html', request=request, context={ 'channel_id': params['channel_id'], 'team_id': integration.external_id, })
def handle(self, request: Request, signed_params: str) -> HttpResponse: if request.method not in ALLOWED_METHODS: return render_error_page(request, body_text="HTTP 405: Method not allowed") params = unsign(signed_params) integration = Integration.objects.get(id=params["integration_id"]) organization = integration.organizations.all()[0] teams = Team.objects.get_for_user(organization, request.user) channel_name = params["channel_name"] channel_id = params["channel_id"] form = SelectTeamForm(teams, request.POST or None) if request.method == "GET": return self.respond( "sentry/integrations/slack-link-team.html", { "form": form, "teams": teams, "channel_name": channel_name, "provider": integration.get_provider(), }, ) if not form.is_valid(): return render_error_page(request, body_text="HTTP 400: Bad request") team_id = form.cleaned_data["team"] try: team = Team.objects.get(id=team_id, organization=organization) except Team.DoesNotExist: return render_error_page(request, body_text="HTTP 404: Team does not exist") try: idp = IdentityProvider.objects.get( type="slack", external_id=integration.external_id) except IdentityProvider.DoesNotExist: logger.error("slack.action.invalid-team-id", extra={"slack_id": integration.external_id}) return render_error_page(request, body_text="HTTP 403: Invalid team ID") try: identity = Identity.objects.select_related("user").get( idp=idp, external_id=params["slack_id"]) except Identity.DoesNotExist: logger.error("slack.action.missing-identity", extra={"slack_id": integration.external_id}) return render_error_page( request, body_text="HTTP 403: User identity does not exist") org_member = OrganizationMember.objects.get(user=identity.user, organization=organization) if not is_valid_role(org_member, team, organization): return send_confirmation( integration, channel_id, INSUFFICIENT_ROLE_TITLE, INSUFFICIENT_ROLE_MESSAGE, "sentry/integrations/slack-post-linked-team.html", request, ) external_team, created = ExternalActor.objects.get_or_create( actor_id=team.actor_id, organization=organization, integration=integration, provider=ExternalProviders.SLACK.value, defaults=dict( external_name=channel_name, external_id=channel_id, ), ) if not created: return send_confirmation( integration, channel_id, ALREADY_LINKED_TITLE, ALREADY_LINKED_MESSAGE.format(slug=team.slug), "sentry/integrations/slack-post-linked-team.html", request, ) # Turn on notifications for all of a team's projects. NotificationSetting.objects.update_settings( ExternalProviders.SLACK, NotificationSettingTypes.ISSUE_ALERTS, NotificationSettingOptionValues.ALWAYS, team=team, ) return send_confirmation( integration, channel_id, SUCCESS_LINKED_TITLE, SUCCESS_LINKED_MESSAGE.format(slug=team.slug, channel_name=channel_name), "sentry/integrations/slack-post-linked-team.html", request, )
def handle(self, request, signed_params): params = unsign(signed_params.encode('ascii', errors='ignore')) try: organization = Organization.objects.get( id__in=request.user.get_orgs(), id=params['organization_id'], ) except Organization.DoesNotExist: raise Http404 try: integration = Integration.objects.get( id=params['integration_id'], organizations=organization, ) except Integration.DoesNotExist: raise Http404 try: idp = IdentityProvider.objects.get( external_id=integration.external_id, type='slack', ) except IdentityProvider.DoesNotExist: raise Http404 if request.method != 'POST': return render_to_response('sentry/auth-link-identity.html', request=request, context={ 'organization': organization, 'provider': integration.get_provider(), }) # TODO(epurkhiser): We could do some fancy slack querying here to # render a nice linking page with info about the user their linking. # Link the user with the identity. Handle the case where the user is linked to a # different identity or the identity is linked to a different user. try: id_by_user = Identity.objects.get(user=request.user, idp=idp) except Identity.DoesNotExist: id_by_user = None try: id_by_external_id = Identity.objects.get( external_id=params['slack_id'], idp=idp) except Identity.DoesNotExist: id_by_external_id = None if not id_by_user and not id_by_external_id: Identity.objects.create( user=request.user, external_id=params['slack_id'], idp=idp, status=IdentityStatus.VALID, ) elif id_by_user and not id_by_external_id: # TODO(epurkhiser): In this case we probably want to prompt and # warn them that they had a previous identity linked to slack. id_by_user.update( external_id=params['slack_id'], status=IdentityStatus.VALID, ) elif id_by_external_id and not id_by_user: id_by_external_id.update( user=request.user, status=IdentityStatus.VALID, ) else: updates = {'status': IdentityStatus.VALID} if id_by_user != id_by_external_id: id_by_external_id.delete() updates['external_id'] = params['slack_id'] id_by_user.update(**updates) payload = { 'replace_original': False, 'response_type': 'ephemeral', 'text': "Your Slack identity has been linked to your Sentry account. You're good to go!" } session = http.build_session() req = session.post(params['response_url'], json=payload) resp = req.json() # If the user took their time to link their slack account, we may no # longer be able to respond, and we're not guaranteed able to post into # the channel. Ignore Expired url errors. # # XXX(epurkhiser): Yes the error string has a space in it. if not resp.get('ok') and resp.get('error') != 'Expired url': logger.error('slack.link-notify.response-error', extra={'response': resp}) return render_to_response('sentry/slack-linked.html', request=request, context={ 'channel_id': params['channel_id'], 'team_id': integration.external_id, })
def handle(self, request: Request, signed_params: str) -> Response: params = unsign(signed_params) organization, integration, idp = get_identity( request.user, params["organization_id"], params["integration_id"] ) channel_name = params["channel_name"] channel_id = params["channel_id"] try: external_team = ExternalActor.objects.get( organization=organization, integration=integration, provider=ExternalProviders.SLACK.value, external_name=channel_name, external_id=channel_id, ) except IntegrityError as e: logger.error("slack.team.unlink.integrity-error", extra=e) raise Http404 team = Team.objects.get(actor=external_team.actor) if request.method != "POST": return render_to_response( "sentry/integrations/slack-unlink-team.html", request=request, context={ "team": team, "channel_name": channel_name, "provider": integration.get_provider(), }, ) try: idp = IdentityProvider.objects.get(type="slack", external_id=integration.external_id) except IdentityProvider.DoesNotExist: logger.error( "slack.action.invalid-team-id", extra={"slack_id": integration.external_id} ) return render_error_page(request, body_text="HTTP 403: Invalid team ID") try: identity = Identity.objects.select_related("user").get( idp=idp, external_id=params["slack_id"] ) except Identity.DoesNotExist: logger.error( "slack.action.missing-identity", extra={"slack_id": integration.external_id} ) return render_error_page(request, body_text="HTTP 403: User identity does not exist") org_member = OrganizationMember.objects.get(user=identity.user, organization=organization) if not is_valid_role(org_member, team, organization): return send_confirmation( integration, channel_id, INSUFFICIENT_ROLE_TITLE, INSUFFICIENT_ROLE_MESSAGE, "sentry/integrations/slack-post-linked-team.html", request, ) external_team.delete() NotificationSetting.objects.remove_for_team(team, ExternalProviders.SLACK) return send_confirmation( integration, channel_id, SUCCESS_UNLINKED_TITLE, SUCCESS_UNLINKED_MESSAGE.format(team=team.slug), "sentry/integrations/slack-unlinked-team.html", request, )