1+ import logging
2+
13from django .core .signing import BadSignature , SignatureExpired
2- from django .http import HttpResponse
4+ from django .db import IntegrityError
5+ from django .http import Http404 , HttpRequest , HttpResponse
6+ from django .http .response import HttpResponseBase
37from django .utils .decorators import method_decorator
48from rest_framework .request import Request
59
10+ from sentry .integrations .slack .views import render_error_page
11+ from sentry .integrations .slack .views .types import IdentityParams
612from sentry .integrations .types import ExternalProviderEnum , ExternalProviders
713from sentry .integrations .utils import get_identity_or_404
814from sentry .models .identity import Identity
915from sentry .notifications .notificationcontroller import NotificationController
1016from sentry .notifications .notifications .integration_nudge import IntegrationNudgeNotification
1117from sentry .services .hybrid_cloud .integration .model import RpcIntegration
18+ from sentry .utils import metrics
1219from sentry .utils .signing import unsign
1320from sentry .web .frontend .base import BaseView , control_silo_view
1421from sentry .web .helpers import render_to_response
1724from . import build_linking_url as base_build_linking_url
1825from . import never_cache
1926
27+ _logger = logging .getLogger (__name__ )
28+
2029SUCCESS_LINKED_MESSAGE = (
2130 "Your Slack identity has been linked to your Sentry account. You're good to go!"
2231)
@@ -40,47 +49,112 @@ class SlackLinkIdentityView(BaseView):
4049 Django view for linking user to slack account. Creates an entry on Identity table.
4150 """
4251
52+ _METRICS_SUCCESS_KEY = "sentry.integrations.slack.link_identity_view.success"
53+ _METRICS_FAILURE_KEY = "sentry.integrations.slack.link_identity_view.failure"
54+
4355 @method_decorator (never_cache )
44- def handle (self , request : Request , signed_params : str ) -> HttpResponse :
56+ def dispatch (self , request : HttpRequest , signed_params : str ) -> HttpResponseBase :
4557 try :
4658 params = unsign (signed_params )
47- except (SignatureExpired , BadSignature ):
59+ except (SignatureExpired , BadSignature ) as e :
60+ _logger .warning ("dispatch.signature_error" , exc_info = e )
61+ metrics .incr (self ._METRICS_FAILURE_KEY , tags = {"error" : str (e )}, sample_rate = 1.0 )
4862 return render_to_response (
4963 "sentry/integrations/slack/expired-link.html" ,
5064 request = request ,
5165 )
5266
53- organization , integration , idp = get_identity_or_404 (
54- ExternalProviders .SLACK ,
55- request .user ,
56- integration_id = params ["integration_id" ],
67+ try :
68+ organization , integration , idp = get_identity_or_404 (
69+ ExternalProviders .SLACK ,
70+ request .user ,
71+ integration_id = params ["integration_id" ],
72+ )
73+ except Http404 :
74+ _logger .exception (
75+ "get_identity_error" , extra = {"integration_id" : params ["integration_id" ]}
76+ )
77+ metrics .incr (self ._METRICS_FAILURE_KEY + ".get_identity" , sample_rate = 1.0 )
78+ return render_error_page (
79+ request ,
80+ status = 404 ,
81+ body_text = "HTTP 404: Could not find the Slack identity." ,
82+ )
83+
84+ _logger .info ("get_identity_success" , extra = {"integration_id" : params ["integration_id" ]})
85+ metrics .incr (self ._METRICS_SUCCESS_KEY + ".get_identity" , sample_rate = 1.0 )
86+ params .update ({"organization" : organization , "integration" : integration , "idp" : idp })
87+ return super ().dispatch (
88+ request , organization = organization , integration = integration , idp = idp , params = params
5789 )
5890
59- if request .method != "POST" :
60- return render_to_response (
61- "sentry/auth-link-identity.html" ,
62- request = request ,
63- context = {"organization" : organization , "provider" : integration .get_provider ()},
91+ def get (self , request : Request , * args , ** kwargs ) -> HttpResponse :
92+ params = kwargs ["params" ]
93+ organization , integration = params ["organization" ], params ["integration" ]
94+
95+ return render_to_response (
96+ "sentry/auth-link-identity.html" ,
97+ request = request ,
98+ context = {"organization" : organization , "provider" : integration .get_provider ()},
99+ )
100+
101+ def post (self , request : Request , * args , ** kwargs ) -> HttpResponse :
102+ try :
103+ params_dict = kwargs ["params" ]
104+ params = IdentityParams (
105+ organization = kwargs ["organization" ],
106+ integration = kwargs ["integration" ],
107+ idp = kwargs ["idp" ],
108+ slack_id = params_dict ["slack_id" ],
109+ channel_id = params_dict ["channel_id" ],
110+ )
111+ except KeyError as e :
112+ _logger .exception ("slack.link.missing_params" )
113+ metrics .incr (
114+ self ._METRICS_FAILURE_KEY + ".post.missing_params" ,
115+ tags = {"error" : str (e )},
116+ sample_rate = 1.0 ,
117+ )
118+ return render_error_page (
119+ request ,
120+ status = 400 ,
121+ body_text = "HTTP 400: Missing required parameters." ,
64122 )
65123
66- Identity .objects .link_identity (user = request .user , idp = idp , external_id = params ["slack_id" ])
124+ try :
125+ Identity .objects .link_identity (
126+ user = request .user , idp = params .idp , external_id = params .slack_id
127+ )
128+ except IntegrityError :
129+ _logger .exception ("slack.link.integrity_error" )
130+ metrics .incr (
131+ self ._METRICS_FAILURE_KEY + ".post.identity.integrity_error" ,
132+ sample_rate = 1.0 ,
133+ )
134+ raise Http404
135+
136+ # TODO: We should use use the dataclass to send the slack response
137+ send_slack_response (
138+ params .integration , SUCCESS_LINKED_MESSAGE , params .__dict__ , command = "link"
139+ )
67140
68- send_slack_response (integration , SUCCESS_LINKED_MESSAGE , params , command = "link" )
69- has_slack_settings = None
70141 controller = NotificationController (
71142 recipients = [request .user ],
72- organization_id = organization .id ,
143+ organization_id = params . organization .id ,
73144 provider = ExternalProviderEnum .SLACK ,
74145 )
75146 has_slack_settings = controller .user_has_any_provider_settings (ExternalProviderEnum .SLACK )
76147
77148 if not has_slack_settings :
78- IntegrationNudgeNotification (organization , request .user , ExternalProviders .SLACK ).send ()
149+ IntegrationNudgeNotification (
150+ params .organization , request .user , ExternalProviders .SLACK
151+ ).send ()
152+
153+ _logger .info ("link_identity_success" , extra = {"slack_id" : params .slack_id })
154+ metrics .incr (self ._METRICS_SUCCESS_KEY + ".post.link_identity" , sample_rate = 1.0 )
79155
80- # TODO(epurkhiser): We could do some fancy slack querying here to
81- # render a nice linking page with info about the user their linking.
82156 return render_to_response (
83157 "sentry/integrations/slack/linked.html" ,
84158 request = request ,
85- context = {"channel_id" : params [ " channel_id" ] , "team_id" : integration .external_id },
159+ context = {"channel_id" : params . channel_id , "team_id" : params . integration .external_id },
86160 )
0 commit comments