Skip to content

Commit f28420c

Browse files
committed
DNM feat(preprod): Add distribution:read scope
1 parent 55bc2ab commit f28420c

File tree

11 files changed

+49
-2
lines changed

11 files changed

+49
-2
lines changed

src/sentry/api/bases/project.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ class ProjectOwnershipPermission(ProjectPermission):
120120
}
121121

122122

123+
class ProjectDistributionPermission(ProjectPermission):
124+
scope_map = {
125+
"GET": ["project:read", "project:write", "project:admin", "distribution:read", "org:ci"],
126+
"POST": ["project:write", "project:admin"],
127+
"PUT": ["project:read", "project:write", "project:admin"],
128+
"DELETE": ["project:admin"],
129+
}
130+
131+
123132
class ProjectEndpoint(Endpoint):
124133
permission_classes: tuple[type[BasePermission], ...] = (ProjectPermission,)
125134

src/sentry/conf/server.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,6 +1727,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
17271727
"event:admin",
17281728
"alerts:read",
17291729
"alerts:write",
1730+
"distribution:read",
17301731
# openid, profile, and email aren't prefixed to maintain compliance with the OIDC spec.
17311732
# https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes.
17321733
"openid",
@@ -1741,6 +1742,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
17411742
"project:read",
17421743
"event:read",
17431744
"alerts:read",
1745+
"distribution:read",
17441746
}
17451747

17461748
SENTRY_SCOPE_HIERARCHY_MAPPING = {
@@ -1765,6 +1767,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
17651767
"event:admin": {"event:read", "event:write", "event:admin"},
17661768
"alerts:read": {"alerts:read"},
17671769
"alerts:write": {"alerts:read", "alerts:write"},
1770+
"distribution:read": {"distribution:read"},
17681771
"openid": {"openid"},
17691772
"profile": {"profile"},
17701773
"email": {"email"},
@@ -1808,6 +1811,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
18081811
("alerts:write", "Read and write alerts"),
18091812
("alerts:read", "Read alerts"),
18101813
),
1814+
(("distribution:read", "Read access to builds."),),
18111815
(("openid", "Confirms authentication status and provides basic information."),),
18121816
(
18131817
(
@@ -1843,6 +1847,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
18431847
"team:read",
18441848
"alerts:read",
18451849
"alerts:write",
1850+
"distribution:read",
18461851
},
18471852
},
18481853
{
@@ -1875,6 +1880,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
18751880
"org:integrations",
18761881
"alerts:read",
18771882
"alerts:write",
1883+
"distribution:read",
18781884
},
18791885
"is_retired": True,
18801886
},
@@ -1902,6 +1908,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
19021908
"org:integrations",
19031909
"alerts:read",
19041910
"alerts:write",
1911+
"distribution:read",
19051912
},
19061913
"is_global": True,
19071914
},
@@ -1936,6 +1943,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
19361943
"event:admin",
19371944
"alerts:read",
19381945
"alerts:write",
1946+
"distribution:read",
19391947
},
19401948
"is_global": True,
19411949
},
@@ -1956,6 +1964,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
19561964
"member:read",
19571965
"team:read",
19581966
"alerts:read",
1967+
"distribution:read",
19591968
# "alerts:write", # Scope granted/withdrawn by "sentry:alerts_member_write" to org-level role
19601969
},
19611970
},
@@ -1985,6 +1994,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
19851994
"org:integrations",
19861995
"alerts:read",
19871996
"alerts:write",
1997+
"distribution:read",
19881998
},
19891999
"is_minimum_role_for": "admin",
19902000
},

src/sentry/models/apiscopes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class ApiScopes(Sequence):
3333

3434
alerts = (("alerts:read"), ("alerts:write"))
3535

36+
distribution = (("distribution:read"),)
37+
3638
def __init__(self):
3739
self.scopes = (
3840
self.__class__.project
@@ -41,6 +43,7 @@ def __init__(self):
4143
+ self.__class__.org
4244
+ self.__class__.member
4345
+ self.__class__.alerts
46+
+ self.__class__.distribution
4447
)
4548

4649
def __getitem__(self, value):
@@ -85,6 +88,7 @@ class Meta:
8588
"alerts:read": bool,
8689
"alerts:write": bool,
8790
"member:invite": bool,
91+
"distribution:read": bool,
8892
},
8993
)
9094
assert set(ScopesDict.__annotations__) == set(ApiScopes())

src/sentry/preprod/api/endpoints/project_preprod_check_for_updates.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from sentry.api.api_owners import ApiOwner
99
from sentry.api.api_publish_status import ApiPublishStatus
1010
from sentry.api.base import region_silo_endpoint
11-
from sentry.api.bases.project import ProjectEndpoint, ProjectReleasePermission
11+
from sentry.api.bases.project import ProjectDistributionPermission, ProjectEndpoint
1212
from sentry.preprod.build_distribution_utils import (
1313
get_download_url_for_artifact,
1414
is_installable_artifact,
@@ -41,7 +41,7 @@ class ProjectPreprodArtifactCheckForUpdatesEndpoint(ProjectEndpoint):
4141
publish_status = {
4242
"GET": ApiPublishStatus.EXPERIMENTAL,
4343
}
44-
permission_classes = (ProjectReleasePermission,)
44+
permission_classes = (ProjectDistributionPermission,)
4545

4646
enforce_rate_limit = True
4747
rate_limits = RateLimitConfig(

static/app/constants/index.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const DEFAULT_APP_ROUTE = USING_CUSTOMER_DOMAIN
3232
export const API_ACCESS_SCOPES = [
3333
'alerts:read',
3434
'alerts:write',
35+
'distribution:read',
3536
'event:admin',
3637
'event:read',
3738
'event:write',
@@ -54,6 +55,7 @@ export const API_ACCESS_SCOPES = [
5455
export const ALLOWED_SCOPES = [
5556
'alerts:read',
5657
'alerts:write',
58+
'distribution:read',
5759
'event:admin',
5860
'event:read',
5961
'event:write',
@@ -208,6 +210,14 @@ export const SENTRY_APP_PERMISSIONS: PermissionObj[] = [
208210
write: {label: 'Read & Write', scopes: ['alerts:read', 'alerts:write']},
209211
},
210212
},
213+
{
214+
resource: 'Distribution',
215+
help: 'Download and install uploaded builds',
216+
choices: {
217+
'no-access': {label: 'No Access', scopes: []},
218+
read: {label: 'Read', scopes: ['distribution:read']},
219+
},
220+
},
211221
];
212222

213223
export const DEFAULT_TOAST_DURATION = 6000;

static/app/types/integrations.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {User} from './user';
1717
export type PermissionValue = 'no-access' | 'read' | 'write' | 'admin';
1818

1919
export type Permissions = {
20+
Distribution: PermissionValue;
2021
Event: PermissionValue;
2122
Member: PermissionValue;
2223
Organization: PermissionValue;

static/app/utils/consolidatedScopes.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const HUMAN_RESOURCE_NAMES = {
1919
org: 'Organization',
2020
member: 'Member',
2121
alerts: 'Alerts',
22+
distribution: 'Distribution',
2223
};
2324

2425
const DEFAULT_RESOURCE_PERMISSIONS: Permissions = {
@@ -29,6 +30,7 @@ const DEFAULT_RESOURCE_PERMISSIONS: Permissions = {
2930
Organization: 'no-access',
3031
Member: 'no-access',
3132
Alerts: 'no-access',
33+
Distribution: 'no-access',
3234
};
3335

3436
const PROJECT_RELEASES = 'project:releases';

static/app/views/settings/account/apiNewToken.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default function ApiNewToken() {
2929
Release: 'no-access',
3030
Organization: 'no-access',
3131
Alerts: 'no-access',
32+
Distribution: 'no-access',
3233
});
3334
const navigate = useNavigate();
3435
const [hasNewToken, setHasnewToken] = useState(false);

static/app/views/settings/organizationDeveloperSettings/permissionSelection.spec.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe('PermissionSelection', () => {
2323
Project: 'write',
2424
Release: 'admin',
2525
Organization: 'admin',
26+
Distribution: 'no-access',
2627
}}
2728
onChange={onChange}
2829
/>
@@ -38,6 +39,7 @@ describe('PermissionSelection', () => {
3839
expect(screen.getByRole('textbox', {name: 'Issue & Event'})).toBeInTheDocument();
3940
expect(screen.getByRole('textbox', {name: 'Organization'})).toBeInTheDocument();
4041
expect(screen.getByRole('textbox', {name: 'Member'})).toBeInTheDocument();
42+
expect(screen.getByRole('textbox', {name: 'Distribution'})).toBeInTheDocument();
4143
});
4244

4345
it('lists human readable permissions', async () => {
@@ -54,6 +56,7 @@ describe('PermissionSelection', () => {
5456
await expectOptions('Issue & Event', ['No Access', 'Read', 'Read & Write', 'Admin']);
5557
await expectOptions('Organization', ['No Access', 'Read', 'Read & Write', 'Admin']);
5658
await expectOptions('Member', ['No Access', 'Read', 'Read & Write', 'Admin']);
59+
await expectOptions('Distribution', ['No Access', 'Read']);
5760
});
5861

5962
it('stores the permissions the User has selected', async () => {
@@ -67,12 +70,14 @@ describe('PermissionSelection', () => {
6770
await selectByValue('Issue & Event', 'Admin');
6871
await selectByValue('Organization', 'Read');
6972
await selectByValue('Member', 'No Access');
73+
await selectByValue('Distribution', 'No Access');
7074

7175
expect(model.getValue('Project--permission')).toBe('write');
7276
expect(model.getValue('Team--permission')).toBe('read');
7377
expect(model.getValue('Release--permission')).toBe('admin');
7478
expect(model.getValue('Event--permission')).toBe('admin');
7579
expect(model.getValue('Organization--permission')).toBe('read');
7680
expect(model.getValue('Member--permission')).toBe('no-access');
81+
expect(model.getValue('Distribution--permission')).toBe('no-access');
7782
});
7883
});

static/app/views/settings/organizationDeveloperSettings/resourceSubscriptions.spec.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ describe('Resource Subscriptions', () => {
1717
Release: 'admin',
1818
Organization: 'admin',
1919
Member: 'admin',
20+
Distribution: 'no-access',
2021
}}
2122
onChange={jest.fn()}
2223
/>
@@ -42,6 +43,7 @@ describe('Resource Subscriptions', () => {
4243
Release: 'admin',
4344
Organization: 'admin',
4445
Member: 'admin',
46+
Distribution: 'no-access',
4547
}}
4648
onChange={jest.fn()}
4749
/>
@@ -67,6 +69,7 @@ describe('Resource Subscriptions', () => {
6769
Release: 'admin',
6870
Organization: 'admin',
6971
Member: 'admin',
72+
Distribution: 'no-access',
7073
}}
7174
onChange={jest.fn()}
7275
/>
@@ -90,6 +93,7 @@ describe('Resource Subscriptions', () => {
9093
Release: 'admin',
9194
Organization: 'admin',
9295
Member: 'admin',
96+
Distribution: 'no-access',
9397
}}
9498
onChange={jest.fn()}
9599
/>

0 commit comments

Comments
 (0)