1
+ from __future__ import annotations
2
+
1
3
from collections .abc import Mapping , MutableMapping
2
4
from typing import Any
3
5
6
+ from django .contrib .auth .models import AnonymousUser
4
7
from django .db import IntegrityError , router , transaction
5
8
from rest_framework .request import Request
6
9
from rest_framework .response import Response
12
15
from sentry .api .bases import GroupEndpoint
13
16
from sentry .api .serializers import serialize
14
17
from sentry .integrations .api .serializers .models .integration import IntegrationSerializer
15
- from sentry .integrations .base import IntegrationFeatures , IntegrationInstallation
18
+ from sentry .integrations .base import IntegrationFeatures
19
+ from sentry .integrations .mixins .issues import IssueBasicIntegration
16
20
from sentry .integrations .models .external_issue import ExternalIssue
21
+ from sentry .integrations .models .integration import Integration
17
22
from sentry .integrations .project_management .metrics import (
18
23
ProjectManagementActionType ,
19
24
ProjectManagementEvent ,
30
35
from sentry .signals import integration_issue_created , integration_issue_linked
31
36
from sentry .types .activity import ActivityType
32
37
from sentry .users .models .user import User
38
+ from sentry .users .services .user .model import RpcUser
33
39
34
40
MISSING_FEATURE_MESSAGE = "Your organization does not have access to this feature."
35
41
@@ -46,7 +52,11 @@ def __init__(
46
52
self .config = config
47
53
48
54
def serialize (
49
- self , obj : RpcIntegration , attrs : Mapping [str , Any ], user : User , ** kwargs : Any
55
+ self ,
56
+ obj : Integration | RpcIntegration ,
57
+ attrs : Mapping [str , Any ],
58
+ user : User | RpcUser | AnonymousUser ,
59
+ ** kwargs : Any ,
50
60
) -> MutableMapping [str , Any ]:
51
61
data = super ().serialize (obj , attrs , user )
52
62
@@ -68,7 +78,7 @@ class GroupIntegrationDetailsEndpoint(GroupEndpoint):
68
78
"POST" : ApiPublishStatus .UNKNOWN ,
69
79
}
70
80
71
- def _has_issue_feature (self , organization , user ):
81
+ def _has_issue_feature (self , organization , user ) -> bool :
72
82
has_issue_basic = features .has (
73
83
"organizations:integrations-issue-basic" , organization , actor = user
74
84
)
@@ -79,16 +89,24 @@ def _has_issue_feature(self, organization, user):
79
89
80
90
return has_issue_sync or has_issue_basic
81
91
82
- def _has_issue_feature_on_integration (self , integration : RpcIntegration ):
92
+ def _has_issue_feature_on_integration (self , integration : RpcIntegration ) -> bool :
83
93
return integration .has_feature (
84
94
feature = IntegrationFeatures .ISSUE_BASIC
85
95
) or integration .has_feature (feature = IntegrationFeatures .ISSUE_SYNC )
86
96
97
+ def _get_installation (
98
+ self , integration : RpcIntegration , organization_id : int
99
+ ) -> IssueBasicIntegration :
100
+ installation = integration .get_installation (organization_id = organization_id )
101
+ if not isinstance (installation , IssueBasicIntegration ):
102
+ raise ValueError (installation )
103
+ return installation
104
+
87
105
def create_issue_activity (
88
106
self ,
89
107
request : Request ,
90
108
group : Group ,
91
- installation : IntegrationInstallation ,
109
+ installation : IssueBasicIntegration ,
92
110
external_issue : ExternalIssue ,
93
111
new : bool ,
94
112
):
@@ -108,7 +126,9 @@ def create_issue_activity(
108
126
)
109
127
110
128
def get (self , request : Request , group , integration_id ) -> Response :
111
- if not self ._has_issue_feature (group .organization , request .user ):
129
+ if not request .user .is_authenticated :
130
+ return Response (status = 400 )
131
+ elif not self ._has_issue_feature (group .organization , request .user ):
112
132
return Response ({"detail" : MISSING_FEATURE_MESSAGE }, status = 400 )
113
133
114
134
# Keep link/create separate since create will likely require
@@ -132,16 +152,16 @@ def get(self, request: Request, group, integration_id) -> Response:
132
152
{"detail" : "This feature is not supported for this integration." }, status = 400
133
153
)
134
154
135
- installation = integration .get_installation (organization_id = organization_id )
136
- config = None
155
+ installation = self ._get_installation (integration , organization_id )
137
156
try :
138
157
if action == "link" :
139
158
config = installation .get_link_issue_config (group , params = request .GET )
140
-
141
- if action == "create" :
159
+ elif action == "create" :
142
160
config = installation .get_create_issue_config (
143
161
group , request .user , params = request .GET
144
162
)
163
+ else :
164
+ raise AssertionError ("unreachable" )
145
165
except IntegrationError as e :
146
166
return Response ({"detail" : str (e )}, status = 400 )
147
167
@@ -156,7 +176,9 @@ def get(self, request: Request, group, integration_id) -> Response:
156
176
157
177
# was thinking put for link an existing issue, post for create new issue?
158
178
def put (self , request : Request , group , integration_id ) -> Response :
159
- if not self ._has_issue_feature (group .organization , request .user ):
179
+ if not request .user .is_authenticated :
180
+ return Response (status = 400 )
181
+ elif not self ._has_issue_feature (group .organization , request .user ):
160
182
return Response ({"detail" : MISSING_FEATURE_MESSAGE }, status = 400 )
161
183
162
184
external_issue_id = request .data .get ("externalIssue" )
@@ -181,7 +203,7 @@ def put(self, request: Request, group, integration_id) -> Response:
181
203
{"detail" : "This feature is not supported for this integration." }, status = 400
182
204
)
183
205
184
- installation = integration . get_installation ( organization_id = organization_id )
206
+ installation = self . _get_installation ( integration , organization_id )
185
207
186
208
try :
187
209
data = installation .get_issue (external_issue_id , data = request .data )
@@ -254,7 +276,9 @@ def put(self, request: Request, group, integration_id) -> Response:
254
276
return Response (context , status = 201 )
255
277
256
278
def post (self , request : Request , group , integration_id ) -> Response :
257
- if not self ._has_issue_feature (group .organization , request .user ):
279
+ if not request .user .is_authenticated :
280
+ return Response (status = 400 )
281
+ elif not self ._has_issue_feature (group .organization , request .user ):
258
282
return Response ({"detail" : MISSING_FEATURE_MESSAGE }, status = 400 )
259
283
260
284
organization_id = group .project .organization_id
@@ -271,7 +295,7 @@ def post(self, request: Request, group, integration_id) -> Response:
271
295
{"detail" : "This feature is not supported for this integration." }, status = 400
272
296
)
273
297
274
- installation = integration . get_installation ( organization_id = organization_id )
298
+ installation = self . _get_installation ( integration , organization_id )
275
299
276
300
with ProjectManagementEvent (
277
301
action_type = ProjectManagementActionType .CREATE_EXTERNAL_ISSUE_VIA_ISSUE_DETAIL ,
0 commit comments