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) 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: 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) 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 create_subscription(self, instance, identity_data, oauth_redirect_url, external_id): client = self.get_client(Identity(data=identity_data), oauth_redirect_url) shared_secret = self.create_webhook_secret() return client.create_subscription(instance, external_id, shared_secret), shared_secret
def create_subscription(self, instance: Optional[str], identity_data: Mapping[str, Any], oauth_redirect_url: str) -> Response: client = self.get_client(Identity(data=identity_data), oauth_redirect_url) shared_secret = generate_token() return client.create_subscription(instance, shared_secret), shared_secret
def create_subscription(self, instance: str | None, oauth_data: Mapping[str, Any]) -> tuple[int, str]: client = VstsApiClient(Identity(data=oauth_data), self.oauth_redirect_url) shared_secret = generate_token() try: subscription = client.create_subscription(instance, shared_secret) except ApiError as e: auth_codes = (400, 401, 403) permission_error = "permission" in str( e) or "not authorized" in str(e) if e.code in auth_codes or permission_error: raise IntegrationProviderError( "You do not have sufficient account access to create webhooks\n" "on the selected Azure DevOps organization.\n" "Please check with the owner of this Azure DevOps account." ) raise e subscription_id = subscription["id"] return subscription_id, shared_secret
def create_subscription(self, instance, identity_data, oauth_redirect_url): client = self.get_client(Identity(data=identity_data), oauth_redirect_url) shared_secret = generate_token() return client.create_subscription(instance, shared_secret), shared_secret
def _finish_pipeline(self, data): if 'reinstall_id' in data: self.integration = Integration.objects.get( provider=self.provider.key, id=data['reinstall_id'], ) self.integration.update(external_id=data['external_id'], status=ObjectStatus.VISIBLE) self.integration.get_installation(self.organization.id).reinstall() elif 'expect_exists' in data: self.integration = Integration.objects.get( provider=self.provider.key, external_id=data['external_id'], ) else: self.integration = ensure_integration(self.provider.key, data) # Does this integration provide a user identity for the user setting up # the integration? identity = data.get('user_identity') if identity: # Some identity providers may not be directly associated to the # external integration. Integrations may specify the external_id to # be used for the idp. idp_external_id = data.get('idp_external_id', data['external_id']) idp_config = data.get('idp_config', {}) # Create identity provider for this integration if necessary idp, created = IdentityProvider.objects.get_or_create( external_id=idp_external_id, type=identity['type'], defaults={'config': idp_config}, ) if not created: idp.update(config=idp_config) identity_data = { 'status': IdentityStatus.VALID, 'scopes': identity['scopes'], 'data': identity['data'], 'date_verified': timezone.now(), } try: identity_model, created = Identity.objects.get_or_create( idp=idp, user=self.request.user, external_id=identity['external_id'], defaults=identity_data, ) if not created: identity_model.update(**identity_data) except IntegrityError: # If the external_id is already used for a different user or # the user already has a different external_id remove those # identities and recreate it, except in the case of Github # where we need to be more careful because users may be using # those identities to log in. if idp.type == 'github': try: other_identity = Identity.objects.get( idp=idp, external_id=identity['external_id'], ) except Identity.DoesNotExist: # The user is linked to a different external_id. It's ok to relink # here because they'll still be able to log in with the new external_id. pass else: # The external_id is linked to a different user. If that user doesn't # have a password, we don't delete the link as it may lock them out. if not other_identity.user.has_usable_password(): return self._dialog_response( { 'error': _('The provided Github account is linked to a different user. ' 'Please try again with a different Github account.' ) }, False, ) identity_model = Identity.reattach(idp, identity['external_id'], self.request.user, identity_data) org_integration_args = {} if self.provider.needs_default_identity: if not (identity and identity_model): raise NotImplementedError('Integration requires an identity') org_integration_args = {'default_auth_id': identity_model.id} org_integration = self.integration.add_organization( self.organization.id, **org_integration_args) return self._dialog_response( serialize(org_integration, self.request.user), True)
def _finish_pipeline(self, data): if "reinstall_id" in data: self.integration = Integration.objects.get( provider=self.provider.integration_key, id=data["reinstall_id"]) self.integration.update(external_id=data["external_id"], status=ObjectStatus.VISIBLE) self.integration.get_installation(self.organization.id).reinstall() if "integration_id" in data: self.integration = Integration.objects.get( provider=self.provider.integration_key, id=data["integration_id"]) self.integration.reauthorize(data) elif "expect_exists" in data: self.integration = Integration.objects.get( provider=self.provider.integration_key, external_id=data["external_id"]) else: self.integration = ensure_integration( self.provider.integration_key, data) # Does this integration provide a user identity for the user setting up # the integration? identity = data.get("user_identity") if identity: # Some identity providers may not be directly associated to the # external integration. Integrations may specify the external_id to # be used for the idp. idp_external_id = data.get("idp_external_id", data["external_id"]) idp_config = data.get("idp_config", {}) # Create identity provider for this integration if necessary idp, created = IdentityProvider.objects.get_or_create( external_id=idp_external_id, type=identity["type"], defaults={"config": idp_config}) if not created: idp.update(config=idp_config) identity_data = { "status": IdentityStatus.VALID, "scopes": identity["scopes"], "data": identity["data"], "date_verified": timezone.now(), } try: identity_model, created = Identity.objects.get_or_create( idp=idp, user=self.request.user, external_id=identity["external_id"], defaults=identity_data, ) if not created: identity_model.update(**identity_data) except IntegrityError: # If the external_id is already used for a different user then throw an error # otherwise we have the same user with a new external id # and we update the identity with the new external_id and identity data try: matched_identity = Identity.objects.get( idp=idp, external_id=identity["external_id"]) except Identity.DoesNotExist: # The user is linked to a different external_id. It's ok to relink # here because they'll still be able to log in with the new external_id. identity_model = Identity.update_external_id_and_defaults( idp, identity["external_id"], self.request.user, identity_data) else: self.get_logger().info( "finish_pipeline.identity_linked_different_user", { "idp_id": idp.id, "external_id": identity["external_id"], "object_id": matched_identity.id, "user_id": self.request.user.id, "type": identity["type"], }, ) # if we don't need a default identity, we don't have to throw an error if self.provider.needs_default_identity: # The external_id is linked to a different user. proper_name = idp.get_provider().name return self._dialog_response( { "error": _("The provided %(proper_name)s account is linked to a different Sentry user. " "To continue linking the current Sentry user, please use a different %(proper_name)s account." ) % ({ "proper_name": proper_name }) }, False, ) default_auth_id = None if self.provider.needs_default_identity: if not (identity and identity_model): raise NotImplementedError("Integration requires an identity") default_auth_id = identity_model.id org_integration = self.integration.add_organization( self.organization, self.request.user, default_auth_id=default_auth_id) return self._dialog_success(org_integration)
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 _finish_pipeline(self, data): if 'reinstall_id' in data: self.integration = Integration.objects.get( provider=self.provider.integration_key, id=data['reinstall_id'], ) self.integration.update(external_id=data['external_id'], status=ObjectStatus.VISIBLE) self.integration.get_installation(self.organization.id).reinstall() elif 'expect_exists' in data: self.integration = Integration.objects.get( provider=self.provider.integration_key, external_id=data['external_id'], ) else: self.integration = ensure_integration( self.provider.integration_key, data, ) # Does this integration provide a user identity for the user setting up # the integration? identity = data.get('user_identity') if identity: # Some identity providers may not be directly associated to the # external integration. Integrations may specify the external_id to # be used for the idp. idp_external_id = data.get('idp_external_id', data['external_id']) idp_config = data.get('idp_config', {}) # Create identity provider for this integration if necessary idp, created = IdentityProvider.objects.get_or_create( external_id=idp_external_id, type=identity['type'], defaults={'config': idp_config}, ) if not created: idp.update(config=idp_config) identity_data = { 'status': IdentityStatus.VALID, 'scopes': identity['scopes'], 'data': identity['data'], 'date_verified': timezone.now(), } try: identity_model, created = Identity.objects.get_or_create( idp=idp, user=self.request.user, external_id=identity['external_id'], defaults=identity_data, ) if not created: identity_model.update(**identity_data) except IntegrityError: # If the external_id is already used for a different user or # the user already has a different external_id remove those # identities and recreate it, except in the case of Github # where we need to be more careful because users may be using # those identities to log in. if idp.type in ('github', 'vsts'): try: other_identity = Identity.objects.get( idp=idp, external_id=identity['external_id'], ) except Identity.DoesNotExist: # The user is linked to a different external_id. It's ok to relink # here because they'll still be able to log in with the new external_id. pass else: # The external_id is linked to a different user. If that user doesn't # have a password, we don't delete the link as it may lock them out. if not other_identity.user.has_usable_password(): proper_name = 'GitHub' if idp.type == 'github' else 'Azure DevOps' return self._dialog_response({ 'error': _( 'The provided %s account is linked to a different user. ' 'Please try again with a different %s account.' ) % (proper_name, proper_name)}, False, ) identity_model = Identity.reattach( idp, identity['external_id'], self.request.user, identity_data) default_auth_id = None if self.provider.needs_default_identity: if not (identity and identity_model): raise NotImplementedError('Integration requires an identity') default_auth_id = identity_model.id org_integration = self.integration.add_organization( self.organization, self.request.user, default_auth_id=default_auth_id) return self._dialog_response(serialize(org_integration, self.request.user), True)
def _finish_pipeline(self, data): if "reinstall_id" in data: self.integration = Integration.objects.get( provider=self.provider.integration_key, id=data["reinstall_id"] ) self.integration.update(external_id=data["external_id"], status=ObjectStatus.VISIBLE) self.integration.get_installation(self.organization.id).reinstall() elif "expect_exists" in data: self.integration = Integration.objects.get( provider=self.provider.integration_key, external_id=data["external_id"] ) else: self.integration = ensure_integration(self.provider.integration_key, data) # Does this integration provide a user identity for the user setting up # the integration? identity = data.get("user_identity") if identity: # Some identity providers may not be directly associated to the # external integration. Integrations may specify the external_id to # be used for the idp. idp_external_id = data.get("idp_external_id", data["external_id"]) idp_config = data.get("idp_config", {}) # Create identity provider for this integration if necessary idp, created = IdentityProvider.objects.get_or_create( external_id=idp_external_id, type=identity["type"], defaults={"config": idp_config} ) if not created: idp.update(config=idp_config) identity_data = { "status": IdentityStatus.VALID, "scopes": identity["scopes"], "data": identity["data"], "date_verified": timezone.now(), } try: identity_model, created = Identity.objects.get_or_create( idp=idp, user=self.request.user, external_id=identity["external_id"], defaults=identity_data, ) if not created: identity_model.update(**identity_data) except IntegrityError: # If the external_id is already used for a different user or # the user already has a different external_id remove those # identities and recreate it, except in the case of Identities # being used for login. if idp.type in ("github", "vsts", "google"): try: other_identity = Identity.objects.get( idp=idp, external_id=identity["external_id"] ) except Identity.DoesNotExist: # The user is linked to a different external_id. It's ok to relink # here because they'll still be able to log in with the new external_id. pass else: # The external_id is linked to a different user. If that user doesn't # have a password, we don't delete the link as it may lock them out. if not other_identity.user.has_usable_password(): proper_name = idp.get_provider().name return self._dialog_response( { "error": _( "The provided %s account is linked to a different Sentry user. " "To continue linking the current Sentry user, please use a different %s account." ) % (proper_name, proper_name) }, False, ) identity_model = Identity.reattach( idp, identity["external_id"], self.request.user, identity_data ) default_auth_id = None if self.provider.needs_default_identity: if not (identity and identity_model): raise NotImplementedError("Integration requires an identity") default_auth_id = identity_model.id org_integration = self.integration.add_organization( self.organization, self.request.user, default_auth_id=default_auth_id ) return self._dialog_response(serialize(org_integration, self.request.user), True)
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) 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, })