Exemple #1
0
    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"]}
        )
Exemple #2
0
    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},
        )
Exemple #3
0
    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
Exemple #5
0
 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
Exemple #6
0
    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
Exemple #7
0
 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
Exemple #8
0
    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)
Exemple #9
0
    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
            },
        )
Exemple #11
0
    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)
Exemple #12
0
    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,
        })