コード例 #1
0
    def test_permission_denied(self):
        user2 = self.create_user(is_superuser=False)

        user2_identity = Identity.objects.create(
            external_id="slack_id2",
            idp=self.idp,
            user=user2,
            status=IdentityStatus.VALID,
            scopes=[],
        )

        status_action = {"name": "status", "value": "ignored", "type": "button"}

        resp = self.post_webhook(
            action_data=[status_action], slack_user={"id": user2_identity.external_id}
        )
        self.group1 = Group.objects.get(id=self.group1.id)

        associate_url = build_unlinking_url(
            self.integration.id, self.org.id, "slack_id2", "C065W1189", self.response_url
        )

        assert resp.status_code == 200, resp.content
        assert resp.data["response_type"] == "ephemeral"
        assert not resp.data["replace_original"]
        assert resp.data["text"] == UNLINK_IDENTITY_MESSAGE.format(
            associate_url=associate_url, user_email=user2.email, org_name=self.org.name
        )
コード例 #2
0
    def test_unlink_user_identity(self):
        self.link_user()
        assert self.find_identity()

        unlinking_url = build_unlinking_url(
            self.integration.id,
            "UXXXXXXX1",
            "CXXXXXXX9",
            "http://example.slack.com/response_url",
        )

        response = self.client.get(unlinking_url)
        assert response.status_code == 200
        self.assertTemplateUsed(response, "sentry/auth-unlink-identity.html")

        response = self.client.post(unlinking_url)
        assert response.status_code == 200
        self.assertTemplateUsed(response, "sentry/integrations/slack/unlinked.html")

        # Assert that the identity was deleted.
        assert not self.find_identity()

        assert len(responses.calls) >= 1
        data = json.loads(str(responses.calls[0].request.body.decode("utf-8")))
        assert SUCCESS_UNLINKED_MESSAGE in data["text"]
コード例 #3
0
    def api_error(
        self,
        slack_request: SlackActionRequest,
        group: Group,
        identity: Identity,
        error: ApiClient.ApiError,
        action_type: str,
    ) -> Response:
        logger.info(
            "slack.action.api-error",
            extra={
                **slack_request.get_logging_data(group),
                "response":
                str(error.body),
                "action_type":
                action_type,
            },
        )

        if error.status_code == 403:
            text = UNLINK_IDENTITY_MESSAGE.format(
                associate_url=build_unlinking_url(
                    slack_request.integration.id,
                    slack_request.user_id,
                    slack_request.channel_id,
                    slack_request.response_url,
                ),
                user_email=identity.user,
                org_name=group.organization.name,
            )
        else:
            text = DEFAULT_ERROR_MESSAGE

        return self.respond_ephemeral(text)
コード例 #4
0
    def test_handle_submission_fail(self, client_put):
        status_action = {"name": "resolve_dialog", "value": "resolve_dialog"}

        # Expect request to open dialog on slack
        responses.add(
            method=responses.POST,
            url="https://slack.com/api/dialog.open",
            body='{"ok": true}',
            status=200,
            content_type="application/json",
        )

        resp = self.post_webhook(action_data=[status_action])
        assert resp.status_code == 200, resp.content

        # Opening dialog should *not* cause the current message to be updated
        assert resp.content == b""

        data = parse_qs(responses.calls[0].request.body)
        assert data["token"][0] == self.integration.metadata["access_token"]
        assert data["trigger_id"][0] == self.trigger_id
        assert "dialog" in data

        dialog = json.loads(data["dialog"][0])
        callback_data = json.loads(dialog["callback_id"])
        assert int(callback_data["issue"]) == self.group.id
        assert callback_data["orig_response_url"] == self.response_url

        # Completing the dialog will update the message
        responses.add(
            method=responses.POST,
            url=self.response_url,
            body='{"ok": true}',
            status=200,
            content_type="application/json",
        )

        # make the client raise an API error
        client_put.side_effect = client.ApiError(
            403, '{"detail":"You do not have permission to perform this action."}'
        )

        resp = self.post_webhook(
            type="dialog_submission",
            callback_id=dialog["callback_id"],
            data={"submission": {"resolve_type": "resolved"}},
        )

        # TODO(mgaeta): `assert_called` is deprecated. Find a replacement.
        # client_put.assert_called()

        associate_url = build_unlinking_url(
            self.integration.id, self.external_id, "C065W1189", self.response_url
        )

        assert resp.status_code == 200, resp.content
        assert resp.data["text"] == UNLINK_IDENTITY_MESSAGE.format(
            associate_url=associate_url, user_email=self.user.email, org_name=self.organization.name
        )
コード例 #5
0
    def setUp(self):
        super().setUp()

        self.unlinking_url = build_unlinking_url(
            self.integration.id,
            self.external_id,
            self.channel_id,
            self.response_url,
        )
コード例 #6
0
    def unlink_user(self, slack_request: SlackRequest) -> Any:
        if not slack_request.has_identity:
            return self.reply(slack_request, NOT_LINKED_MESSAGE)

        integration = slack_request.integration
        associate_url = build_unlinking_url(
            integration_id=integration.id,
            slack_id=slack_request.user_id,
            channel_id=slack_request.channel_id,
            response_url=slack_request.response_url,
        )
        return self.reply(slack_request, UNLINK_USER_MESSAGE.format(associate_url=associate_url))
コード例 #7
0
    def unlink_user(self, slack_request: SlackCommandRequest) -> Response:
        if not slack_request.has_identity:
            return self.send_ephemeral_notification(NOT_LINKED_MESSAGE)

        integration = slack_request.integration
        organization = integration.organizations.all()[0]
        associate_url = build_unlinking_url(
            integration_id=integration.id,
            organization_id=organization.id,
            slack_id=slack_request.user_id,
            channel_id=slack_request.channel_id,
            response_url=slack_request.response_url,
        )
        return self.send_ephemeral_notification(
            UNLINK_USER_MESSAGE.format(associate_url=associate_url))
コード例 #8
0
    def test_unlink_user_identity(self):
        self.link_user()

        unlinking_url = build_unlinking_url(
            self.integration.id,
            self.slack_id,
            self.external_id,
            self.response_url,
        )

        response = self.client.post(unlinking_url)
        assert response.status_code == 200

        assert len(responses.calls) >= 1
        data = json.loads(str(responses.calls[0].request.body.decode("utf-8")))
        assert SUCCESS_UNLINKED_MESSAGE in get_response_text(data)
コード例 #9
0
    def unlink_user(self, slack_request: SlackDMRequest) -> Response:
        if not slack_request.has_identity:
            return self.reply(slack_request, NOT_LINKED_MESSAGE)

        if not (slack_request.integration and slack_request.user_id
                and slack_request.channel_id):
            raise SlackRequestError(status=status.HTTP_400_BAD_REQUEST)

        associate_url = build_unlinking_url(
            integration_id=slack_request.integration.id,
            slack_id=slack_request.user_id,
            channel_id=slack_request.channel_id,
            response_url=slack_request.response_url,
        )
        return self.reply(
            slack_request,
            UNLINK_USER_MESSAGE.format(associate_url=associate_url))
コード例 #10
0
    def test_basic_flow(self, unsign):

        Identity.objects.create(
            user=self.user1, idp=self.idp, external_id="new-slack-id", status=IdentityStatus.VALID
        )

        unsign.return_value = {
            "integration_id": self.integration.id,
            "organization_id": self.org.id,
            "slack_id": "new-slack-id",
            "channel_id": "my-channel",
            "response_url": "http://example.slack.com/response_url",
        }

        unlinking_url = build_unlinking_url(
            self.integration.id,
            self.org.id,
            "new-slack-id",
            "my-channel",
            "http://example.slack.com/response_url",
        )

        resp = self.client.get(unlinking_url)

        assert resp.status_code == 200
        self.assertTemplateUsed(resp, "sentry/auth-unlink-identity.html")

        responses.add(
            method=responses.POST,
            url="http://example.slack.com/response_url",
            body='{"ok": true}',
            status=200,
            content_type="application/json",
        )

        # Unlink identity of user
        resp = self.client.post(unlinking_url)

        identity = Identity.objects.filter(external_id="new-slack-id", user=self.user1)

        assert len(identity) == 0
        assert len(responses.calls) == 1
コード例 #11
0
ファイル: action.py プロジェクト: KingDEV95/sentry
    def post(self, request: Request) -> Response:
        logging_data: Dict[str, str] = {}

        try:
            slack_request = SlackActionRequest(request)
            slack_request.validate()
        except SlackRequestError as e:
            return self.respond(status=e.status)

        data = slack_request.data

        # Actions list may be empty when receiving a dialog response
        action_list = data.get("actions", [])
        action_option = action_list and action_list[0].get("value", "")

        # if a user is just clicking our auto response in the messages tab we just return a 200
        if action_option == "sentry_docs_link_clicked":
            return self.respond()

        channel_id = slack_request.channel_id
        user_id = slack_request.user_id
        integration = slack_request.integration
        response_url = data.get("response_url")

        if action_option in ["link", "ignore"]:
            analytics.record(
                "integrations.slack.chart_unfurl_action",
                organization_id=integration.organizations.all()[0].id,
                action=action_option,
            )
            payload = {"delete_original": "true"}
            try:
                post(response_url, json=payload)
            except ApiError as e:
                logger.error("slack.action.response-error",
                             extra={"error": str(e)})
                return self.respond(status=403)

            return self.respond()

        logging_data["channel_id"] = channel_id
        logging_data["slack_user_id"] = user_id
        logging_data["response_url"] = response_url
        logging_data["integration_id"] = integration.id

        # Determine the issue group action is being taken on
        group_id = slack_request.callback_data["issue"]
        logging_data["group_id"] = group_id

        try:
            group = Group.objects.select_related("project__organization").get(
                project__in=Project.objects.filter(
                    organization__in=integration.organizations.all()),
                id=group_id,
            )
        except Group.DoesNotExist:
            logger.info("slack.action.invalid-issue", extra=logging_data)
            return self.respond(status=403)

        logging_data["organization_id"] = group.organization.id

        # Determine the acting user by slack identity
        try:
            idp = IdentityProvider.objects.get(
                type="slack", external_id=slack_request.team_id)
        except IdentityProvider.DoesNotExist:
            logger.error("slack.action.invalid-team-id", extra=logging_data)
            return self.respond(status=403)

        try:
            identity = Identity.objects.select_related("user").get(
                idp=idp, external_id=user_id)
        except Identity.DoesNotExist:
            associate_url = build_linking_url(integration, group.organization,
                                              user_id, channel_id,
                                              response_url)

            return self.respond({
                "response_type":
                "ephemeral",
                "replace_original":
                False,
                "text":
                LINK_IDENTITY_MESSAGE.format(associate_url=associate_url),
            })

        # Handle status dialog submission
        if slack_request.type == "dialog_submission" and "resolve_type" in data[
                "submission"]:
            # Masquerade a status action
            action = {
                "name": "status",
                "value": data["submission"]["resolve_type"]
            }

            try:
                self.on_status(request, identity, group, action, data,
                               integration)
            except client.ApiError as e:

                if e.status_code == 403:
                    text = UNLINK_IDENTITY_MESSAGE.format(
                        associate_url=build_unlinking_url(
                            integration.id, user_id, channel_id, response_url),
                        user_email=identity.user,
                        org_name=group.organization.name,
                    )
                else:
                    text = DEFAULT_ERROR_MESSAGE

                return self.api_error(e, "status_dialog", logging_data, text)

            group = Group.objects.get(id=group.id)
            attachment = build_group_attachment(group,
                                                identity=identity,
                                                actions=[action])

            body = self.construct_reply(
                attachment,
                is_message=slack_request.callback_data["is_message"])

            # use the original response_url to update the link attachment
            slack_client = SlackClient()
            try:
                slack_client.post(
                    slack_request.callback_data["orig_response_url"],
                    data=body,
                    json=True)
            except ApiError as e:
                logger.error("slack.action.response-error",
                             extra={"error": str(e)})

            return self.respond()

        # Usually we'll want to respond with the updated attachment including
        # the list of actions taken. However, when opening a dialog we do not
        # have anything to update the message with and will use the
        # response_url later to update it.
        defer_attachment_update = False

        # Handle interaction actions
        action_type = None
        try:
            for action in action_list:
                action_type = action["name"]

                if action_type == "status":
                    self.on_status(request, identity, group, action, data,
                                   integration)
                elif action_type == "assign":
                    self.on_assign(request, identity, group, action)
                elif action_type == "resolve_dialog":
                    self.open_resolve_dialog(data, group, integration)
                    defer_attachment_update = True
        except client.ApiError as e:
            if e.status_code == 403:
                text = UNLINK_IDENTITY_MESSAGE.format(
                    associate_url=build_unlinking_url(integration.id, user_id,
                                                      channel_id,
                                                      response_url),
                    user_email=identity.user,
                    org_name=group.organization.name,
                )
            else:
                text = DEFAULT_ERROR_MESSAGE

            return self.api_error(e, action_type, logging_data, text)

        if defer_attachment_update:
            return self.respond()

        # Reload group as it may have been mutated by the action
        group = Group.objects.get(id=group.id)

        attachment = build_group_attachment(group,
                                            identity=identity,
                                            actions=action_list)
        body = self.construct_reply(attachment,
                                    is_message=self.is_message(data))

        return self.respond(body)
コード例 #12
0
ファイル: test_action.py プロジェクト: wangjianweiwei/sentry
    def test_handle_submission_fail(self):
        status_action = {"name": "resolve_dialog", "value": "resolve_dialog"}

        # Expect request to open dialog on slack
        responses.add(
            method=responses.POST,
            url="https://slack.com/api/dialog.open",
            body='{"ok": true}',
            status=200,
            content_type="application/json",
        )

        resp = self.post_webhook(action_data=[status_action])
        assert resp.status_code == 200, resp.content

        # Opening dialog should *not* cause the current message to be updated
        assert resp.content == b""

        data = parse_qs(responses.calls[0].request.body)
        assert data["token"][0] == self.integration.metadata["access_token"]
        assert data["trigger_id"][0] == self.trigger_id
        assert "dialog" in data

        dialog = json.loads(data["dialog"][0])
        callback_data = json.loads(dialog["callback_id"])
        assert int(callback_data["issue"]) == self.group.id
        assert callback_data["orig_response_url"] == self.response_url

        # Completing the dialog will update the message
        responses.add(
            method=responses.POST,
            url=self.response_url,
            body='{"ok": true}',
            status=200,
            content_type="application/json",
        )

        # Remove the user from the organization.
        member = OrganizationMember.objects.get(user=self.user,
                                                organization=self.organization)
        member.remove_user()
        member.save()

        response = self.post_webhook(
            type="dialog_submission",
            callback_id=dialog["callback_id"],
            data={"submission": {
                "resolve_type": "resolved"
            }},
        )

        assert response.status_code == 200, response.content
        assert response.data["text"] == UNLINK_IDENTITY_MESSAGE.format(
            associate_url=build_unlinking_url(
                integration_id=self.integration.id,
                slack_id=self.external_id,
                channel_id="C065W1189",
                response_url=self.response_url,
            ),
            user_email=self.user.email,
            org_name=self.organization.name,
        )