Skip to content

Commit c0f476d

Browse files
committed
Merge branch 'master' into tkdodo/ref/keys-endpoint-to-apiOptions
2 parents 5fe6d55 + 4244ff0 commit c0f476d

File tree

319 files changed

+9068
-25605
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

319 files changed

+9068
-25605
lines changed

.github/file-filters.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
sentry_frontend_workflow_file: &sentry_frontend_workflow_file
44
- added|modified: '.github/workflows/frontend.yml'
55

6+
sentry_frontend_snapshots_workflow_file: &sentry_frontend_snapshots_workflow_file
7+
- added|modified: '.github/workflows/frontend-snapshots.yml'
8+
69
# Provides list of changed files to test (jest)
710
# getsentry/sentry does not use the list directly, instead we shard tests inside jest.config.js
811
testable_modified: &testable_modified
@@ -30,6 +33,7 @@ typecheckable_rules_changed: &typecheckable_rules_changed
3033
# Trigger to apply the 'Scope: Frontend' label to PRs
3134
frontend_all: &frontend_all
3235
- *sentry_frontend_workflow_file
36+
- *sentry_frontend_snapshots_workflow_file
3337
- added|modified: '**/*.{ts,tsx,js,jsx,mjs}'
3438
- added|modified: 'static/**/*.{less,json,yml,md,mdx}'
3539
- added|modified: '{vercel,tsconfig,biome,package}.json'

.github/workflows/frontend-optional.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,17 @@ jobs:
9090

9191
- uses: ./.github/actions/setup-node-pnpm
9292

93+
- name: jest transform cache
94+
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
95+
with:
96+
path: |
97+
.cache/jest
98+
~/.cache/swc
99+
key: jest-cache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'jest.config.ts') }}-${{ matrix.instance }}
100+
restore-keys: |
101+
jest-cache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'jest.config.ts') }}-
102+
jest-cache-${{ runner.os }}-
103+
93104
- name: Download jest-balance.json
94105
id: download-artifact
95106
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11

.github/workflows/frontend-snapshots.yml

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -69,37 +69,19 @@ jobs:
6969

7070
- name: Install sentry-cli
7171
if: ${{ !cancelled() }}
72-
run: curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION=3.3.4 sh
72+
run: curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION=3.3.5 sh
7373

7474
- name: Upload snapshots
7575
id: upload
7676
if: ${{ !cancelled() }}
7777
env:
7878
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_SNAPSHOTS_AUTH_TOKEN }}
79-
run: |
80-
ARGS=(
81-
--log-level=debug
82-
--auth-token "${{ secrets.SENTRY_SNAPSHOTS_AUTH_TOKEN }}"
83-
build snapshots "${{ env.SNAPSHOT_OUTPUT_DIR }}"
84-
--app-id sentry-frontend
85-
--project sentry-frontend
86-
--head-sha "${{ github.event.pull_request.head.sha || github.sha }}"
87-
--vcs-provider github
88-
--head-repo-name "${{ github.repository }}"
89-
)
90-
91-
# PR-only flags: base-sha, base-ref, base-repo-name, head-ref, pr-number
92-
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
93-
ARGS+=(
94-
--base-sha "${{ github.event.pull_request.base.sha }}"
95-
--base-repo-name "${{ github.repository }}"
96-
--head-ref "${{ github.head_ref }}"
97-
--base-ref "${{ github.base_ref }}"
98-
--pr-number "${{ github.event.number }}"
99-
)
100-
fi
101-
102-
sentry-cli "${ARGS[@]}"
79+
run: >
80+
sentry-cli
81+
--log-level=debug
82+
build snapshots "${{ env.SNAPSHOT_OUTPUT_DIR }}"
83+
--app-id sentry-frontend
84+
--project sentry-frontend
10385
10486
- name: Report upload failure to Sentry
10587
if: ${{ failure() && steps.upload.outcome == 'failure' }}

CHANGES

Lines changed: 2286 additions & 0 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ dependencies = [
9090
"sentry-protos>=0.8.12",
9191
"sentry-redis-tools>=0.5.0",
9292
"sentry-relay>=0.9.27",
93+
"sentry-scm>=0.1.7",
9394
"sentry-sdk[http2]>=2.47.0",
9495
"sentry-usage-accountant>=0.0.10",
9596
# remove once there are no unmarked transitive dependencies on setuptools

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = sentry
3-
version = 26.4.0.dev0
3+
version = 26.5.0.dev0
44
description = A realtime logging and aggregation server.
55
long_description = file: README.md
66
long_description_content_type = text/markdown

src/sentry/api/authentication.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,10 @@ def authenticate(self, request: Request) -> tuple[Any, Any] | None:
833833
return None
834834

835835
sentry_sdk.get_isolation_scope().set_tag("viewer_context_auth", True)
836+
# Viewer context comes from a trusted first-party service. Keep auth
837+
# session-like for permission derivation, but mark it so org access can
838+
# avoid requiring browser-session SSO state on service callbacks.
839+
setattr(request, "user_from_viewer_context", True)
836840

837841
# Return None for auth to match session behavior —
838842
# determine_access will derive scopes from org membership role.

src/sentry/api/endpoints/internal/feature_flags.py

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from collections.abc import Mapping
2+
13
from django.conf import settings
24
from rest_framework.request import Request
35
from rest_framework.response import Response
@@ -10,6 +12,30 @@
1012
from sentry.runner.settings import configure, discover_configs
1113

1214

15+
def _coerce_early_feature_flag_value(value: object) -> bool | None:
16+
"""
17+
Map API input to a bool for SENTRY_FEATURES. Accepts JSON booleans, 0/1
18+
(common in scripts), and the strings "true"/"false" (case-insensitive).
19+
Returns None if the value cannot be interpreted safely.
20+
"""
21+
if isinstance(value, bool):
22+
return value
23+
if isinstance(value, int):
24+
if value == 1:
25+
return True
26+
if value == 0:
27+
return False
28+
return None
29+
if isinstance(value, str):
30+
lowered = value.lower()
31+
if lowered == "true":
32+
return True
33+
if lowered == "false":
34+
return False
35+
return None
36+
return None
37+
38+
1339
@all_silo_endpoint
1440
class InternalFeatureFlagsEndpoint(Endpoint):
1541
permission_classes = (SuperuserPermission,)
@@ -36,18 +62,38 @@ def put(self, request: Request) -> Response:
3662
if not settings.SENTRY_SELF_HOSTED:
3763
return Response("You are not self-hosting Sentry.", status=403)
3864

39-
data = request.data.keys()
40-
valid_feature_flags = [flag for flag in data if SENTRY_EARLY_FEATURES.get(flag, False)]
65+
payload: object = request.data
66+
if not isinstance(payload, Mapping):
67+
return Response(
68+
{"detail": "Feature flag updates must be a JSON object."},
69+
status=400,
70+
)
71+
72+
valid_feature_flags = [flag for flag in payload if flag in SENTRY_EARLY_FEATURES]
73+
coerced_values: dict[str, bool] = {}
74+
for valid_flag in valid_feature_flags:
75+
coerced = _coerce_early_feature_flag_value(payload.get(valid_flag))
76+
if coerced is None:
77+
return Response(
78+
{
79+
"detail": (
80+
f'Feature flag "{valid_flag}" must be true or false '
81+
f"(boolean, 0 or 1, or the string true or false)."
82+
)
83+
},
84+
status=400,
85+
)
86+
coerced_values[valid_flag] = coerced
87+
4188
_, py, yml = discover_configs()
4289
# Open the file for reading and writing
4390
with open(py, "r+") as file:
4491
lines = file.readlines()
4592
# print(lines)
4693
for valid_flag in valid_feature_flags:
4794
match_found = False
48-
new_string = (
49-
f'\nSENTRY_FEATURES["{valid_flag}"]={request.data.get(valid_flag, False)}\n'
50-
)
95+
python_bool = "True" if coerced_values[valid_flag] else "False"
96+
new_string = f'\nSENTRY_FEATURES["{valid_flag}"]={python_bool}\n'
5197
# Search for the string match and update lines
5298
for i, line in enumerate(lines):
5399
if valid_flag in line:

src/sentry/api/endpoints/organization_spans_fields.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@
3636
from sentry.utils import snuba_rpc
3737

3838

39-
def as_tag_key(name: str, type: Literal["string", "number", "boolean"]):
40-
key, _, _ = translate_internal_to_public_alias(name, type, SupportedTraceItemType.SPANS)
39+
def as_tag_key(name: str, search_type: Literal["string", "number", "boolean"]):
40+
key, _, _ = translate_internal_to_public_alias(name, search_type, SupportedTraceItemType.SPANS)
4141

4242
if key is not None:
4343
name = key
44-
elif type == "number":
44+
elif search_type == "number":
4545
key = f"tags[{name},number]"
46-
elif type == "boolean":
46+
elif search_type == "boolean":
4747
key = f"tags[{name},boolean]"
4848
else:
4949
key = name

src/sentry/api/permissions.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,10 @@ def determine_access(
203203
rpc_user_org_context=org_context,
204204
)
205205

206-
if auth.is_user_signed_request(request):
207-
# if the user comes from a signed request
208-
# we let them pass if sso is enabled
206+
if auth.is_user_signed_request(request) or auth.is_user_from_viewer_context(request):
207+
# Signed requests and viewer-context-authenticated service
208+
# callbacks already carry a trusted assertion of user identity, so
209+
# they should not depend on browser-session SSO completion.
209210
logger.info(
210211
"access.signed-sso-passthrough",
211212
extra=extra,

0 commit comments

Comments
 (0)