feat: add OrgAuthToken model - #50409

Merged
mydea merged 2 commits into
masterfrom
fn/org-auth-token
Jun 14, 2023
Merged

feat: add OrgAuthToken model#50409
mydea merged 2 commits into
masterfrom
fn/org-auth-token

Conversation

@mydea

@mydeamydea commented Jun 6, 2023

Copy link
Copy Markdown
Member

This adds a new OrgAuthToken model:

  • Tied to an organization
  • Has a token which is expected to be a JWT token
  • Add two basic utilities for token generation/parsing
  • It is possible to assign project(s) to a token

ref #50144

based on RFC getsentry/rfcs#91

@mydeamydea self-assigned this Jun 6, 2023
@mydea
mydea requested a review from a team as a code ownerJune 6, 2023 12:20
@github-actionsgithub-actionsBot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 6, 2023
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure what I should make the max length here. on the RFC there is a comment that we should store this in hashed form, if we do this I guess we need a more generic length here...?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks! I supposed it was for security reasons then it makes sense.

Here we should make the length, the length of the hash. I don't expect we will need to store the token in any other format besides the hashed one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to be explicit that we store token_hashed in the DB.

@codecov

codecovBot commented Jun 6, 2023

Copy link
Copy Markdown

Codecov Report

Merging #50409 (7518568) into master (12d0ad9) will increase coverage by 1.01%.
The diff coverage is 87.87%.

Additional details and impacted files
@@ Coverage Diff @@## master #50409 +/- ##
==========================================
+ Coverage 80.14% 81.15% +1.01% 
==========================================
Files 4843 4845 +2 Lines 203733 203799 +66 Branches 11130 11130 ==========================================
+ Hits 163275 165401 +2126 + Misses 40212 38152 -2060 
Partials 246 246 
Impacted FilesCoverage Δ
src/sentry/models/orgauthtoken.py86.66% <86.66%> (ø)
src/sentry/utils/security/orgauthtoken_jwt.py90.00% <90.00%> (ø)
src/sentry/models/__init__.py100.00% <100.00%> (ø)

... and 113 files with indirect coverage changes

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 69eb8a8 to 5d1c4f2CompareJune 7, 2023 07:41
@mydea

mydea commented Jun 7, 2023

Copy link
Copy Markdown
MemberAuthor

Note: I put these new models into the control silo, mirroring the api token model, I think that is the correct place?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should put this model in orgauthtoken.py since it belongs to the same cluster of models related to org auth tokens.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after talking this through with @mitsuhiko , we decided to skip the projects for now!

@iambriccardoiambriccardoJun 7, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the nature of the relationship, it's not a big deal at all for the db.

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we statically type the list of scopes? Having such a generic field is a bit suspicious.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for reference, this is how the scope list is currently stored in other models. But we may improve on this here :D

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible i would like to bound it list of elements, maybe like:

fromdjango.dbimportmodelsfromdjango.core.exceptionsimportValidationErrorclassMyModel(models.Model):
OPTIONS= [
('option1', 'Option 1'),
('option2', 'Option 2'),
('option3', 'Option 3'),
]
choices_array=models.ArrayField(
models.TextField(),
validators=[],
)
defvalidate_choices_array(value):
forchoiceinvalue:
ifchoicenotindict(MyModel.OPTIONS).keys():
raiseValidationError(f"{choice} is not a valid choice.")
choices_array.validators.append(validate_choices_array)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I implemented this!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_added refers to the User that created this token? If so, I would call this user or added_by.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the idea - I'll rename it to added_by (IMHO user is a potentially confusing as it may imply this token belongs to that user, which it doesn't).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, that's a good point!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a null field, we could have it default to timezone.now and update it when the user uses it, but this changes the semantics. I don't know which ones are more representative of the situation.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it's useful to know that the token was never used, I'd say?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could technically infer it if the two dates are equal but ofc it's not the best way. My main concern was that having nullability in the db is not very nice but I understand your point.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want cascading here? If the last used project is deleted should the org token be deleted?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point @markstory, I missed this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mydea we can just put the project id here as a BoundedBigIntegerField and forget about possible accidental cascading deletions.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point, true! So it should def. not cascade, but should we use HubridCloudForeignKey or BoundedBigIntegerField then? Happy to use BoundedBigIntegerField, just wanting to make sure to get the hybrid could stuff right...!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(BTW also created_by should not cascade but set to null as well, adjusted that too!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be use even when the instance is self-hosted/single-tenant?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, valid question... took this from here: https://github.com/getsentry/rfcs/pull/91/files#diff-3109d1f30b4b81085e85d841deafefb0d1b43cc5345c64514475512a4ce9fdeeR94

IMHO it should be something consistent that says "this is from sentry", and not differ when it is self-hosted/single-tenant. Not sure if sentry.io is the best value for this then, but I'd say it's fine, maybe (just "Sentry" may also be ambiguous, ...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll also need the organizations 'root' URL as in the future, we'll have multiple regions (with different API urls). While customer traffic can continue using https://sentry.io/api/0/** there will be latency overhead (and cost to us) so we should encourage using regional domains instead.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this different from "sentry_site": settings.SENTRY_OPTIONS["system.url-prefix"],? that is what this is designed to do (but honestly I am not 100% sure what fields are what there, @mitsuhiko pointed me to this settings field)

So to be clear, the only point of the sentry_site field is exactly what you mentioned, if we should store something else in there, happy to be pointed to it!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, organizations will have a couple URLs:

  • sentryUrl which is our root domain and is the same as system.url-prefix. ex. sentry.io
  • organizationUrl which is domain for the organization's UI. ex acme.sentry.io
  • regionUrl Which is the domain that the organization's API endpoints are available on. ex us.sentry.io

Any API requests sent to sentryUrl will be routed to the correct region, but will suffer a latency penalty (because we're proxying the request to the region) and the request will go to the US (this can matter for EU customers).

For this scenario, I think you'll want both the sentryUrl and regionUrl available in the JWT. There are a small number of API endpoints that are only available on sentryUrl (users, integrations, sentry apps).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do I get regionUrl from? (I left a comment in the RFC as well to make sure this is aligned - I'd go with sentry_url and sentry_region_url then)

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project_last_used=HybridCloudForeignKey(
"sentry.Project", null=True, blank=True, on_delete="cascade"
)
project_last_used=BoundedBigIntegerFieldd(db_index=True)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this preferred over the HybridCouldForeignKey? 🤔 Feels like that is designed for exactly this use case, but honestly not entirely sure... 😅

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option would be to use HybridCloudForeignKey("sentry.Project", null=True, blank=True, on_delete="SET_NULL")

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the expertise to judge what is better, since I lack in-depth knowledge of hybrid cloud. @markstory could you give an input here, thanks!

@AniketDas-TekkyAniketDas-Tekky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying my comment from the issue:
#50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 3c20c78 to fcd2cccCompareJune 9, 2023 07:56
@github-actions

github-actionsBot commented Jun 9, 2023

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0483_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"varchar(3000) NOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" varchar_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea

mydea commented Jun 9, 2023

Copy link
Copy Markdown
MemberAuthor

Copying my comment from the issue: #50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

Hey, there is an RFC getsentry/rfcs#91 by @mitsuhiko. If you have any concerns about this on a fundamental level, that's the place to discuss this, I guess. My understanding is that the basic decision that we want to add new org-level tokens has been taken, but @mitsuhiko can probably give you more information if you have more fundamental questions!

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadpyproject.toml Outdated
@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0487_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0488_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea
mydea dismissed AniketDas-Tekky’s stale reviewJune 14, 2023 11:50

I think this has been addressed!

@mydea
mydea merged commit fdc8f5a into masterJun 14, 2023
@mydea
mydea deleted the fn/org-auth-token branch June 14, 2023 12:17
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: BackendAutomatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@mydea@markstory@iambriccardo@mdtro@AniketDas-Tekky@asottile-sentry
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: add OrgAuthToken model - #50409

Merged
mydea merged 2 commits into
masterfrom
fn/org-auth-token
Jun 14, 2023
Merged

feat: add OrgAuthToken model#50409
mydea merged 2 commits into
masterfrom
fn/org-auth-token

Conversation

@mydea

@mydeamydea commented Jun 6, 2023

Copy link
Copy Markdown
Member

This adds a new OrgAuthToken model:

  • Tied to an organization
  • Has a token which is expected to be a JWT token
  • Add two basic utilities for token generation/parsing
  • It is possible to assign project(s) to a token

ref #50144

based on RFC getsentry/rfcs#91

@mydeamydea self-assigned this Jun 6, 2023
@mydea
mydea requested a review from a team as a code ownerJune 6, 2023 12:20
@github-actionsgithub-actionsBot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 6, 2023
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure what I should make the max length here. on the RFC there is a comment that we should store this in hashed form, if we do this I guess we need a more generic length here...?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks! I supposed it was for security reasons then it makes sense.

Here we should make the length, the length of the hash. I don't expect we will need to store the token in any other format besides the hashed one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to be explicit that we store token_hashed in the DB.

@codecov

codecovBot commented Jun 6, 2023

Copy link
Copy Markdown

Codecov Report

Merging #50409 (7518568) into master (12d0ad9) will increase coverage by 1.01%.
The diff coverage is 87.87%.

Additional details and impacted files
@@ Coverage Diff @@## master #50409 +/- ##
==========================================
+ Coverage 80.14% 81.15% +1.01% 
==========================================
Files 4843 4845 +2 Lines 203733 203799 +66 Branches 11130 11130 ==========================================
+ Hits 163275 165401 +2126 + Misses 40212 38152 -2060 
Partials 246 246 
Impacted FilesCoverage Δ
src/sentry/models/orgauthtoken.py86.66% <86.66%> (ø)
src/sentry/utils/security/orgauthtoken_jwt.py90.00% <90.00%> (ø)
src/sentry/models/__init__.py100.00% <100.00%> (ø)

... and 113 files with indirect coverage changes

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 69eb8a8 to 5d1c4f2CompareJune 7, 2023 07:41
@mydea

mydea commented Jun 7, 2023

Copy link
Copy Markdown
MemberAuthor

Note: I put these new models into the control silo, mirroring the api token model, I think that is the correct place?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should put this model in orgauthtoken.py since it belongs to the same cluster of models related to org auth tokens.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after talking this through with @mitsuhiko , we decided to skip the projects for now!

@iambriccardoiambriccardoJun 7, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the nature of the relationship, it's not a big deal at all for the db.

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we statically type the list of scopes? Having such a generic field is a bit suspicious.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for reference, this is how the scope list is currently stored in other models. But we may improve on this here :D

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible i would like to bound it list of elements, maybe like:

fromdjango.dbimportmodelsfromdjango.core.exceptionsimportValidationErrorclassMyModel(models.Model):
OPTIONS= [
('option1', 'Option 1'),
('option2', 'Option 2'),
('option3', 'Option 3'),
]
choices_array=models.ArrayField(
models.TextField(),
validators=[],
)
defvalidate_choices_array(value):
forchoiceinvalue:
ifchoicenotindict(MyModel.OPTIONS).keys():
raiseValidationError(f"{choice} is not a valid choice.")
choices_array.validators.append(validate_choices_array)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I implemented this!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_added refers to the User that created this token? If so, I would call this user or added_by.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the idea - I'll rename it to added_by (IMHO user is a potentially confusing as it may imply this token belongs to that user, which it doesn't).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, that's a good point!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a null field, we could have it default to timezone.now and update it when the user uses it, but this changes the semantics. I don't know which ones are more representative of the situation.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it's useful to know that the token was never used, I'd say?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could technically infer it if the two dates are equal but ofc it's not the best way. My main concern was that having nullability in the db is not very nice but I understand your point.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want cascading here? If the last used project is deleted should the org token be deleted?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point @markstory, I missed this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mydea we can just put the project id here as a BoundedBigIntegerField and forget about possible accidental cascading deletions.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point, true! So it should def. not cascade, but should we use HubridCloudForeignKey or BoundedBigIntegerField then? Happy to use BoundedBigIntegerField, just wanting to make sure to get the hybrid could stuff right...!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(BTW also created_by should not cascade but set to null as well, adjusted that too!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be use even when the instance is self-hosted/single-tenant?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, valid question... took this from here: https://github.com/getsentry/rfcs/pull/91/files#diff-3109d1f30b4b81085e85d841deafefb0d1b43cc5345c64514475512a4ce9fdeeR94

IMHO it should be something consistent that says "this is from sentry", and not differ when it is self-hosted/single-tenant. Not sure if sentry.io is the best value for this then, but I'd say it's fine, maybe (just "Sentry" may also be ambiguous, ...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll also need the organizations 'root' URL as in the future, we'll have multiple regions (with different API urls). While customer traffic can continue using https://sentry.io/api/0/** there will be latency overhead (and cost to us) so we should encourage using regional domains instead.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this different from "sentry_site": settings.SENTRY_OPTIONS["system.url-prefix"],? that is what this is designed to do (but honestly I am not 100% sure what fields are what there, @mitsuhiko pointed me to this settings field)

So to be clear, the only point of the sentry_site field is exactly what you mentioned, if we should store something else in there, happy to be pointed to it!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, organizations will have a couple URLs:

  • sentryUrl which is our root domain and is the same as system.url-prefix. ex. sentry.io
  • organizationUrl which is domain for the organization's UI. ex acme.sentry.io
  • regionUrl Which is the domain that the organization's API endpoints are available on. ex us.sentry.io

Any API requests sent to sentryUrl will be routed to the correct region, but will suffer a latency penalty (because we're proxying the request to the region) and the request will go to the US (this can matter for EU customers).

For this scenario, I think you'll want both the sentryUrl and regionUrl available in the JWT. There are a small number of API endpoints that are only available on sentryUrl (users, integrations, sentry apps).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do I get regionUrl from? (I left a comment in the RFC as well to make sure this is aligned - I'd go with sentry_url and sentry_region_url then)

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project_last_used=HybridCloudForeignKey(
"sentry.Project", null=True, blank=True, on_delete="cascade"
)
project_last_used=BoundedBigIntegerFieldd(db_index=True)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this preferred over the HybridCouldForeignKey? 🤔 Feels like that is designed for exactly this use case, but honestly not entirely sure... 😅

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option would be to use HybridCloudForeignKey("sentry.Project", null=True, blank=True, on_delete="SET_NULL")

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the expertise to judge what is better, since I lack in-depth knowledge of hybrid cloud. @markstory could you give an input here, thanks!

@AniketDas-TekkyAniketDas-Tekky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying my comment from the issue:
#50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 3c20c78 to fcd2cccCompareJune 9, 2023 07:56
@github-actions

github-actionsBot commented Jun 9, 2023

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0483_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"varchar(3000) NOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" varchar_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea

mydea commented Jun 9, 2023

Copy link
Copy Markdown
MemberAuthor

Copying my comment from the issue: #50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

Hey, there is an RFC getsentry/rfcs#91 by @mitsuhiko. If you have any concerns about this on a fundamental level, that's the place to discuss this, I guess. My understanding is that the basic decision that we want to add new org-level tokens has been taken, but @mitsuhiko can probably give you more information if you have more fundamental questions!

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadpyproject.toml Outdated
@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0487_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0488_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea
mydea dismissed AniketDas-Tekky’s stale reviewJune 14, 2023 11:50

I think this has been addressed!

@mydea
mydea merged commit fdc8f5a into masterJun 14, 2023
@mydea
mydea deleted the fn/org-auth-token branch June 14, 2023 12:17
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: BackendAutomatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@mydea@markstory@iambriccardo@mdtro@AniketDas-Tekky@asottile-sentry
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add OrgAuthToken model - #50409

Merged
mydea merged 2 commits into
masterfrom
fn/org-auth-token
Jun 14, 2023
Merged

feat: add OrgAuthToken model#50409
mydea merged 2 commits into
masterfrom
fn/org-auth-token

Conversation

@mydea

@mydeamydea commented Jun 6, 2023

Copy link
Copy Markdown
Member

This adds a new OrgAuthToken model:

  • Tied to an organization
  • Has a token which is expected to be a JWT token
  • Add two basic utilities for token generation/parsing
  • It is possible to assign project(s) to a token

ref #50144

based on RFC getsentry/rfcs#91

@mydeamydea self-assigned this Jun 6, 2023
@mydea
mydea requested a review from a team as a code ownerJune 6, 2023 12:20
@github-actionsgithub-actionsBot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 6, 2023
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure what I should make the max length here. on the RFC there is a comment that we should store this in hashed form, if we do this I guess we need a more generic length here...?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks! I supposed it was for security reasons then it makes sense.

Here we should make the length, the length of the hash. I don't expect we will need to store the token in any other format besides the hashed one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to be explicit that we store token_hashed in the DB.

@codecov

codecovBot commented Jun 6, 2023

Copy link
Copy Markdown

Codecov Report

Merging #50409 (7518568) into master (12d0ad9) will increase coverage by 1.01%.
The diff coverage is 87.87%.

Additional details and impacted files
@@ Coverage Diff @@## master #50409 +/- ##
==========================================
+ Coverage 80.14% 81.15% +1.01% 
==========================================
Files 4843 4845 +2 Lines 203733 203799 +66 Branches 11130 11130 ==========================================
+ Hits 163275 165401 +2126 + Misses 40212 38152 -2060 
Partials 246 246 
Impacted FilesCoverage Δ
src/sentry/models/orgauthtoken.py86.66% <86.66%> (ø)
src/sentry/utils/security/orgauthtoken_jwt.py90.00% <90.00%> (ø)
src/sentry/models/__init__.py100.00% <100.00%> (ø)

... and 113 files with indirect coverage changes

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 69eb8a8 to 5d1c4f2CompareJune 7, 2023 07:41
@mydea

mydea commented Jun 7, 2023

Copy link
Copy Markdown
MemberAuthor

Note: I put these new models into the control silo, mirroring the api token model, I think that is the correct place?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should put this model in orgauthtoken.py since it belongs to the same cluster of models related to org auth tokens.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after talking this through with @mitsuhiko , we decided to skip the projects for now!

@iambriccardoiambriccardoJun 7, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the nature of the relationship, it's not a big deal at all for the db.

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we statically type the list of scopes? Having such a generic field is a bit suspicious.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for reference, this is how the scope list is currently stored in other models. But we may improve on this here :D

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible i would like to bound it list of elements, maybe like:

fromdjango.dbimportmodelsfromdjango.core.exceptionsimportValidationErrorclassMyModel(models.Model):
OPTIONS= [
('option1', 'Option 1'),
('option2', 'Option 2'),
('option3', 'Option 3'),
]
choices_array=models.ArrayField(
models.TextField(),
validators=[],
)
defvalidate_choices_array(value):
forchoiceinvalue:
ifchoicenotindict(MyModel.OPTIONS).keys():
raiseValidationError(f"{choice} is not a valid choice.")
choices_array.validators.append(validate_choices_array)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I implemented this!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_added refers to the User that created this token? If so, I would call this user or added_by.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the idea - I'll rename it to added_by (IMHO user is a potentially confusing as it may imply this token belongs to that user, which it doesn't).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, that's a good point!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a null field, we could have it default to timezone.now and update it when the user uses it, but this changes the semantics. I don't know which ones are more representative of the situation.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it's useful to know that the token was never used, I'd say?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could technically infer it if the two dates are equal but ofc it's not the best way. My main concern was that having nullability in the db is not very nice but I understand your point.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want cascading here? If the last used project is deleted should the org token be deleted?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point @markstory, I missed this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mydea we can just put the project id here as a BoundedBigIntegerField and forget about possible accidental cascading deletions.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point, true! So it should def. not cascade, but should we use HubridCloudForeignKey or BoundedBigIntegerField then? Happy to use BoundedBigIntegerField, just wanting to make sure to get the hybrid could stuff right...!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(BTW also created_by should not cascade but set to null as well, adjusted that too!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be use even when the instance is self-hosted/single-tenant?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, valid question... took this from here: https://github.com/getsentry/rfcs/pull/91/files#diff-3109d1f30b4b81085e85d841deafefb0d1b43cc5345c64514475512a4ce9fdeeR94

IMHO it should be something consistent that says "this is from sentry", and not differ when it is self-hosted/single-tenant. Not sure if sentry.io is the best value for this then, but I'd say it's fine, maybe (just "Sentry" may also be ambiguous, ...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll also need the organizations 'root' URL as in the future, we'll have multiple regions (with different API urls). While customer traffic can continue using https://sentry.io/api/0/** there will be latency overhead (and cost to us) so we should encourage using regional domains instead.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this different from "sentry_site": settings.SENTRY_OPTIONS["system.url-prefix"],? that is what this is designed to do (but honestly I am not 100% sure what fields are what there, @mitsuhiko pointed me to this settings field)

So to be clear, the only point of the sentry_site field is exactly what you mentioned, if we should store something else in there, happy to be pointed to it!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, organizations will have a couple URLs:

  • sentryUrl which is our root domain and is the same as system.url-prefix. ex. sentry.io
  • organizationUrl which is domain for the organization's UI. ex acme.sentry.io
  • regionUrl Which is the domain that the organization's API endpoints are available on. ex us.sentry.io

Any API requests sent to sentryUrl will be routed to the correct region, but will suffer a latency penalty (because we're proxying the request to the region) and the request will go to the US (this can matter for EU customers).

For this scenario, I think you'll want both the sentryUrl and regionUrl available in the JWT. There are a small number of API endpoints that are only available on sentryUrl (users, integrations, sentry apps).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do I get regionUrl from? (I left a comment in the RFC as well to make sure this is aligned - I'd go with sentry_url and sentry_region_url then)

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project_last_used=HybridCloudForeignKey(
"sentry.Project", null=True, blank=True, on_delete="cascade"
)
project_last_used=BoundedBigIntegerFieldd(db_index=True)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this preferred over the HybridCouldForeignKey? 🤔 Feels like that is designed for exactly this use case, but honestly not entirely sure... 😅

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option would be to use HybridCloudForeignKey("sentry.Project", null=True, blank=True, on_delete="SET_NULL")

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the expertise to judge what is better, since I lack in-depth knowledge of hybrid cloud. @markstory could you give an input here, thanks!

@AniketDas-TekkyAniketDas-Tekky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying my comment from the issue:
#50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 3c20c78 to fcd2cccCompareJune 9, 2023 07:56
@github-actions

github-actionsBot commented Jun 9, 2023

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0483_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"varchar(3000) NOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" varchar_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea

mydea commented Jun 9, 2023

Copy link
Copy Markdown
MemberAuthor

Copying my comment from the issue: #50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

Hey, there is an RFC getsentry/rfcs#91 by @mitsuhiko. If you have any concerns about this on a fundamental level, that's the place to discuss this, I guess. My understanding is that the basic decision that we want to add new org-level tokens has been taken, but @mitsuhiko can probably give you more information if you have more fundamental questions!

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadpyproject.toml Outdated
@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0487_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0488_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea
mydea dismissed AniketDas-Tekky’s stale reviewJune 14, 2023 11:50

I think this has been addressed!

@mydea
mydea merged commit fdc8f5a into masterJun 14, 2023
@mydea
mydea deleted the fn/org-auth-token branch June 14, 2023 12:17
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: BackendAutomatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@mydea@markstory@iambriccardo@mdtro@AniketDas-Tekky@asottile-sentry
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add OrgAuthToken model - #50409

Merged
mydea merged 2 commits into
masterfrom
fn/org-auth-token
Jun 14, 2023
Merged

feat: add OrgAuthToken model#50409
mydea merged 2 commits into
masterfrom
fn/org-auth-token

Conversation

@mydea

@mydeamydea commented Jun 6, 2023

Copy link
Copy Markdown
Member

This adds a new OrgAuthToken model:

  • Tied to an organization
  • Has a token which is expected to be a JWT token
  • Add two basic utilities for token generation/parsing
  • It is possible to assign project(s) to a token

ref #50144

based on RFC getsentry/rfcs#91

@mydeamydea self-assigned this Jun 6, 2023
@mydea
mydea requested a review from a team as a code ownerJune 6, 2023 12:20
@github-actionsgithub-actionsBot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 6, 2023
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure what I should make the max length here. on the RFC there is a comment that we should store this in hashed form, if we do this I guess we need a more generic length here...?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks! I supposed it was for security reasons then it makes sense.

Here we should make the length, the length of the hash. I don't expect we will need to store the token in any other format besides the hashed one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to be explicit that we store token_hashed in the DB.

@codecov

codecovBot commented Jun 6, 2023

Copy link
Copy Markdown

Codecov Report

Merging #50409 (7518568) into master (12d0ad9) will increase coverage by 1.01%.
The diff coverage is 87.87%.

Additional details and impacted files
@@ Coverage Diff @@## master #50409 +/- ##
==========================================
+ Coverage 80.14% 81.15% +1.01% 
==========================================
Files 4843 4845 +2 Lines 203733 203799 +66 Branches 11130 11130 ==========================================
+ Hits 163275 165401 +2126 + Misses 40212 38152 -2060 
Partials 246 246 
Impacted FilesCoverage Δ
src/sentry/models/orgauthtoken.py86.66% <86.66%> (ø)
src/sentry/utils/security/orgauthtoken_jwt.py90.00% <90.00%> (ø)
src/sentry/models/__init__.py100.00% <100.00%> (ø)

... and 113 files with indirect coverage changes

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 69eb8a8 to 5d1c4f2CompareJune 7, 2023 07:41
@mydea

mydea commented Jun 7, 2023

Copy link
Copy Markdown
MemberAuthor

Note: I put these new models into the control silo, mirroring the api token model, I think that is the correct place?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should put this model in orgauthtoken.py since it belongs to the same cluster of models related to org auth tokens.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after talking this through with @mitsuhiko , we decided to skip the projects for now!

@iambriccardoiambriccardoJun 7, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the nature of the relationship, it's not a big deal at all for the db.

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we statically type the list of scopes? Having such a generic field is a bit suspicious.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for reference, this is how the scope list is currently stored in other models. But we may improve on this here :D

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible i would like to bound it list of elements, maybe like:

fromdjango.dbimportmodelsfromdjango.core.exceptionsimportValidationErrorclassMyModel(models.Model):
OPTIONS= [
('option1', 'Option 1'),
('option2', 'Option 2'),
('option3', 'Option 3'),
]
choices_array=models.ArrayField(
models.TextField(),
validators=[],
)
defvalidate_choices_array(value):
forchoiceinvalue:
ifchoicenotindict(MyModel.OPTIONS).keys():
raiseValidationError(f"{choice} is not a valid choice.")
choices_array.validators.append(validate_choices_array)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I implemented this!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_added refers to the User that created this token? If so, I would call this user or added_by.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the idea - I'll rename it to added_by (IMHO user is a potentially confusing as it may imply this token belongs to that user, which it doesn't).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, that's a good point!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a null field, we could have it default to timezone.now and update it when the user uses it, but this changes the semantics. I don't know which ones are more representative of the situation.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it's useful to know that the token was never used, I'd say?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could technically infer it if the two dates are equal but ofc it's not the best way. My main concern was that having nullability in the db is not very nice but I understand your point.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want cascading here? If the last used project is deleted should the org token be deleted?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point @markstory, I missed this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mydea we can just put the project id here as a BoundedBigIntegerField and forget about possible accidental cascading deletions.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point, true! So it should def. not cascade, but should we use HubridCloudForeignKey or BoundedBigIntegerField then? Happy to use BoundedBigIntegerField, just wanting to make sure to get the hybrid could stuff right...!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(BTW also created_by should not cascade but set to null as well, adjusted that too!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be use even when the instance is self-hosted/single-tenant?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, valid question... took this from here: https://github.com/getsentry/rfcs/pull/91/files#diff-3109d1f30b4b81085e85d841deafefb0d1b43cc5345c64514475512a4ce9fdeeR94

IMHO it should be something consistent that says "this is from sentry", and not differ when it is self-hosted/single-tenant. Not sure if sentry.io is the best value for this then, but I'd say it's fine, maybe (just "Sentry" may also be ambiguous, ...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll also need the organizations 'root' URL as in the future, we'll have multiple regions (with different API urls). While customer traffic can continue using https://sentry.io/api/0/** there will be latency overhead (and cost to us) so we should encourage using regional domains instead.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this different from "sentry_site": settings.SENTRY_OPTIONS["system.url-prefix"],? that is what this is designed to do (but honestly I am not 100% sure what fields are what there, @mitsuhiko pointed me to this settings field)

So to be clear, the only point of the sentry_site field is exactly what you mentioned, if we should store something else in there, happy to be pointed to it!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, organizations will have a couple URLs:

  • sentryUrl which is our root domain and is the same as system.url-prefix. ex. sentry.io
  • organizationUrl which is domain for the organization's UI. ex acme.sentry.io
  • regionUrl Which is the domain that the organization's API endpoints are available on. ex us.sentry.io

Any API requests sent to sentryUrl will be routed to the correct region, but will suffer a latency penalty (because we're proxying the request to the region) and the request will go to the US (this can matter for EU customers).

For this scenario, I think you'll want both the sentryUrl and regionUrl available in the JWT. There are a small number of API endpoints that are only available on sentryUrl (users, integrations, sentry apps).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do I get regionUrl from? (I left a comment in the RFC as well to make sure this is aligned - I'd go with sentry_url and sentry_region_url then)

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project_last_used=HybridCloudForeignKey(
"sentry.Project", null=True, blank=True, on_delete="cascade"
)
project_last_used=BoundedBigIntegerFieldd(db_index=True)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this preferred over the HybridCouldForeignKey? 🤔 Feels like that is designed for exactly this use case, but honestly not entirely sure... 😅

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option would be to use HybridCloudForeignKey("sentry.Project", null=True, blank=True, on_delete="SET_NULL")

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the expertise to judge what is better, since I lack in-depth knowledge of hybrid cloud. @markstory could you give an input here, thanks!

@AniketDas-TekkyAniketDas-Tekky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying my comment from the issue:
#50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 3c20c78 to fcd2cccCompareJune 9, 2023 07:56
@github-actions

github-actionsBot commented Jun 9, 2023

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0483_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"varchar(3000) NOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" varchar_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea

mydea commented Jun 9, 2023

Copy link
Copy Markdown
MemberAuthor

Copying my comment from the issue: #50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

Hey, there is an RFC getsentry/rfcs#91 by @mitsuhiko. If you have any concerns about this on a fundamental level, that's the place to discuss this, I guess. My understanding is that the basic decision that we want to add new org-level tokens has been taken, but @mitsuhiko can probably give you more information if you have more fundamental questions!

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadpyproject.toml Outdated
@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0487_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0488_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea
mydea dismissed AniketDas-Tekky’s stale reviewJune 14, 2023 11:50

I think this has been addressed!

@mydea
mydea merged commit fdc8f5a into masterJun 14, 2023
@mydea
mydea deleted the fn/org-auth-token branch June 14, 2023 12:17
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: BackendAutomatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@mydea@markstory@iambriccardo@mdtro@AniketDas-Tekky@asottile-sentry
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: add OrgAuthToken model - #50409

Merged
mydea merged 2 commits into
masterfrom
fn/org-auth-token
Jun 14, 2023
Merged

feat: add OrgAuthToken model#50409
mydea merged 2 commits into
masterfrom
fn/org-auth-token

Conversation

@mydea

@mydeamydea commented Jun 6, 2023

Copy link
Copy Markdown
Member

This adds a new OrgAuthToken model:

  • Tied to an organization
  • Has a token which is expected to be a JWT token
  • Add two basic utilities for token generation/parsing
  • It is possible to assign project(s) to a token

ref #50144

based on RFC getsentry/rfcs#91

@mydeamydea self-assigned this Jun 6, 2023
@mydea
mydea requested a review from a team as a code ownerJune 6, 2023 12:20
@github-actionsgithub-actionsBot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 6, 2023
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure what I should make the max length here. on the RFC there is a comment that we should store this in hashed form, if we do this I guess we need a more generic length here...?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks! I supposed it was for security reasons then it makes sense.

Here we should make the length, the length of the hash. I don't expect we will need to store the token in any other format besides the hashed one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to be explicit that we store token_hashed in the DB.

@codecov

codecovBot commented Jun 6, 2023

Copy link
Copy Markdown

Codecov Report

Merging #50409 (7518568) into master (12d0ad9) will increase coverage by 1.01%.
The diff coverage is 87.87%.

Additional details and impacted files
@@ Coverage Diff @@## master #50409 +/- ##
==========================================
+ Coverage 80.14% 81.15% +1.01% 
==========================================
Files 4843 4845 +2 Lines 203733 203799 +66 Branches 11130 11130 ==========================================
+ Hits 163275 165401 +2126 + Misses 40212 38152 -2060 
Partials 246 246 
Impacted FilesCoverage Δ
src/sentry/models/orgauthtoken.py86.66% <86.66%> (ø)
src/sentry/utils/security/orgauthtoken_jwt.py90.00% <90.00%> (ø)
src/sentry/models/__init__.py100.00% <100.00%> (ø)

... and 113 files with indirect coverage changes

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 69eb8a8 to 5d1c4f2CompareJune 7, 2023 07:41
@mydea

mydea commented Jun 7, 2023

Copy link
Copy Markdown
MemberAuthor

Note: I put these new models into the control silo, mirroring the api token model, I think that is the correct place?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should put this model in orgauthtoken.py since it belongs to the same cluster of models related to org auth tokens.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after talking this through with @mitsuhiko , we decided to skip the projects for now!

@iambriccardoiambriccardoJun 7, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the nature of the relationship, it's not a big deal at all for the db.

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we statically type the list of scopes? Having such a generic field is a bit suspicious.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for reference, this is how the scope list is currently stored in other models. But we may improve on this here :D

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible i would like to bound it list of elements, maybe like:

fromdjango.dbimportmodelsfromdjango.core.exceptionsimportValidationErrorclassMyModel(models.Model):
OPTIONS= [
('option1', 'Option 1'),
('option2', 'Option 2'),
('option3', 'Option 3'),
]
choices_array=models.ArrayField(
models.TextField(),
validators=[],
)
defvalidate_choices_array(value):
forchoiceinvalue:
ifchoicenotindict(MyModel.OPTIONS).keys():
raiseValidationError(f"{choice} is not a valid choice.")
choices_array.validators.append(validate_choices_array)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I implemented this!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_added refers to the User that created this token? If so, I would call this user or added_by.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the idea - I'll rename it to added_by (IMHO user is a potentially confusing as it may imply this token belongs to that user, which it doesn't).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, that's a good point!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a null field, we could have it default to timezone.now and update it when the user uses it, but this changes the semantics. I don't know which ones are more representative of the situation.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it's useful to know that the token was never used, I'd say?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could technically infer it if the two dates are equal but ofc it's not the best way. My main concern was that having nullability in the db is not very nice but I understand your point.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want cascading here? If the last used project is deleted should the org token be deleted?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point @markstory, I missed this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mydea we can just put the project id here as a BoundedBigIntegerField and forget about possible accidental cascading deletions.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point, true! So it should def. not cascade, but should we use HubridCloudForeignKey or BoundedBigIntegerField then? Happy to use BoundedBigIntegerField, just wanting to make sure to get the hybrid could stuff right...!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(BTW also created_by should not cascade but set to null as well, adjusted that too!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be use even when the instance is self-hosted/single-tenant?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, valid question... took this from here: https://github.com/getsentry/rfcs/pull/91/files#diff-3109d1f30b4b81085e85d841deafefb0d1b43cc5345c64514475512a4ce9fdeeR94

IMHO it should be something consistent that says "this is from sentry", and not differ when it is self-hosted/single-tenant. Not sure if sentry.io is the best value for this then, but I'd say it's fine, maybe (just "Sentry" may also be ambiguous, ...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll also need the organizations 'root' URL as in the future, we'll have multiple regions (with different API urls). While customer traffic can continue using https://sentry.io/api/0/** there will be latency overhead (and cost to us) so we should encourage using regional domains instead.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this different from "sentry_site": settings.SENTRY_OPTIONS["system.url-prefix"],? that is what this is designed to do (but honestly I am not 100% sure what fields are what there, @mitsuhiko pointed me to this settings field)

So to be clear, the only point of the sentry_site field is exactly what you mentioned, if we should store something else in there, happy to be pointed to it!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, organizations will have a couple URLs:

  • sentryUrl which is our root domain and is the same as system.url-prefix. ex. sentry.io
  • organizationUrl which is domain for the organization's UI. ex acme.sentry.io
  • regionUrl Which is the domain that the organization's API endpoints are available on. ex us.sentry.io

Any API requests sent to sentryUrl will be routed to the correct region, but will suffer a latency penalty (because we're proxying the request to the region) and the request will go to the US (this can matter for EU customers).

For this scenario, I think you'll want both the sentryUrl and regionUrl available in the JWT. There are a small number of API endpoints that are only available on sentryUrl (users, integrations, sentry apps).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do I get regionUrl from? (I left a comment in the RFC as well to make sure this is aligned - I'd go with sentry_url and sentry_region_url then)

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project_last_used=HybridCloudForeignKey(
"sentry.Project", null=True, blank=True, on_delete="cascade"
)
project_last_used=BoundedBigIntegerFieldd(db_index=True)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this preferred over the HybridCouldForeignKey? 🤔 Feels like that is designed for exactly this use case, but honestly not entirely sure... 😅

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option would be to use HybridCloudForeignKey("sentry.Project", null=True, blank=True, on_delete="SET_NULL")

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the expertise to judge what is better, since I lack in-depth knowledge of hybrid cloud. @markstory could you give an input here, thanks!

@AniketDas-TekkyAniketDas-Tekky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying my comment from the issue:
#50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 3c20c78 to fcd2cccCompareJune 9, 2023 07:56
@github-actions

github-actionsBot commented Jun 9, 2023

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0483_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"varchar(3000) NOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" varchar_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea

mydea commented Jun 9, 2023

Copy link
Copy Markdown
MemberAuthor

Copying my comment from the issue: #50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

Hey, there is an RFC getsentry/rfcs#91 by @mitsuhiko. If you have any concerns about this on a fundamental level, that's the place to discuss this, I guess. My understanding is that the basic decision that we want to add new org-level tokens has been taken, but @mitsuhiko can probably give you more information if you have more fundamental questions!

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadpyproject.toml Outdated
@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0487_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0488_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea
mydea dismissed AniketDas-Tekky’s stale reviewJune 14, 2023 11:50

I think this has been addressed!

@mydea
mydea merged commit fdc8f5a into masterJun 14, 2023
@mydea
mydea deleted the fn/org-auth-token branch June 14, 2023 12:17
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: BackendAutomatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@mydea@markstory@iambriccardo@mdtro@AniketDas-Tekky@asottile-sentry
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add OrgAuthToken model - #50409

Merged
mydea merged 2 commits into
masterfrom
fn/org-auth-token
Jun 14, 2023
Merged

feat: add OrgAuthToken model#50409
mydea merged 2 commits into
masterfrom
fn/org-auth-token

Conversation

@mydea

@mydeamydea commented Jun 6, 2023

Copy link
Copy Markdown
Member

This adds a new OrgAuthToken model:

  • Tied to an organization
  • Has a token which is expected to be a JWT token
  • Add two basic utilities for token generation/parsing
  • It is possible to assign project(s) to a token

ref #50144

based on RFC getsentry/rfcs#91

@mydeamydea self-assigned this Jun 6, 2023
@mydea
mydea requested a review from a team as a code ownerJune 6, 2023 12:20
@github-actionsgithub-actionsBot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 6, 2023
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure what I should make the max length here. on the RFC there is a comment that we should store this in hashed form, if we do this I guess we need a more generic length here...?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks! I supposed it was for security reasons then it makes sense.

Here we should make the length, the length of the hash. I don't expect we will need to store the token in any other format besides the hashed one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to be explicit that we store token_hashed in the DB.

@codecov

codecovBot commented Jun 6, 2023

Copy link
Copy Markdown

Codecov Report

Merging #50409 (7518568) into master (12d0ad9) will increase coverage by 1.01%.
The diff coverage is 87.87%.

Additional details and impacted files
@@ Coverage Diff @@## master #50409 +/- ##
==========================================
+ Coverage 80.14% 81.15% +1.01% 
==========================================
Files 4843 4845 +2 Lines 203733 203799 +66 Branches 11130 11130 ==========================================
+ Hits 163275 165401 +2126 + Misses 40212 38152 -2060 
Partials 246 246 
Impacted FilesCoverage Δ
src/sentry/models/orgauthtoken.py86.66% <86.66%> (ø)
src/sentry/utils/security/orgauthtoken_jwt.py90.00% <90.00%> (ø)
src/sentry/models/__init__.py100.00% <100.00%> (ø)

... and 113 files with indirect coverage changes

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 69eb8a8 to 5d1c4f2CompareJune 7, 2023 07:41
@mydea

mydea commented Jun 7, 2023

Copy link
Copy Markdown
MemberAuthor

Note: I put these new models into the control silo, mirroring the api token model, I think that is the correct place?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should put this model in orgauthtoken.py since it belongs to the same cluster of models related to org auth tokens.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after talking this through with @mitsuhiko , we decided to skip the projects for now!

@iambriccardoiambriccardoJun 7, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the nature of the relationship, it's not a big deal at all for the db.

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we statically type the list of scopes? Having such a generic field is a bit suspicious.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for reference, this is how the scope list is currently stored in other models. But we may improve on this here :D

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible i would like to bound it list of elements, maybe like:

fromdjango.dbimportmodelsfromdjango.core.exceptionsimportValidationErrorclassMyModel(models.Model):
OPTIONS= [
('option1', 'Option 1'),
('option2', 'Option 2'),
('option3', 'Option 3'),
]
choices_array=models.ArrayField(
models.TextField(),
validators=[],
)
defvalidate_choices_array(value):
forchoiceinvalue:
ifchoicenotindict(MyModel.OPTIONS).keys():
raiseValidationError(f"{choice} is not a valid choice.")
choices_array.validators.append(validate_choices_array)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I implemented this!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_added refers to the User that created this token? If so, I would call this user or added_by.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the idea - I'll rename it to added_by (IMHO user is a potentially confusing as it may imply this token belongs to that user, which it doesn't).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, that's a good point!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a null field, we could have it default to timezone.now and update it when the user uses it, but this changes the semantics. I don't know which ones are more representative of the situation.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it's useful to know that the token was never used, I'd say?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could technically infer it if the two dates are equal but ofc it's not the best way. My main concern was that having nullability in the db is not very nice but I understand your point.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want cascading here? If the last used project is deleted should the org token be deleted?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point @markstory, I missed this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mydea we can just put the project id here as a BoundedBigIntegerField and forget about possible accidental cascading deletions.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point, true! So it should def. not cascade, but should we use HubridCloudForeignKey or BoundedBigIntegerField then? Happy to use BoundedBigIntegerField, just wanting to make sure to get the hybrid could stuff right...!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(BTW also created_by should not cascade but set to null as well, adjusted that too!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be use even when the instance is self-hosted/single-tenant?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, valid question... took this from here: https://github.com/getsentry/rfcs/pull/91/files#diff-3109d1f30b4b81085e85d841deafefb0d1b43cc5345c64514475512a4ce9fdeeR94

IMHO it should be something consistent that says "this is from sentry", and not differ when it is self-hosted/single-tenant. Not sure if sentry.io is the best value for this then, but I'd say it's fine, maybe (just "Sentry" may also be ambiguous, ...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll also need the organizations 'root' URL as in the future, we'll have multiple regions (with different API urls). While customer traffic can continue using https://sentry.io/api/0/** there will be latency overhead (and cost to us) so we should encourage using regional domains instead.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this different from "sentry_site": settings.SENTRY_OPTIONS["system.url-prefix"],? that is what this is designed to do (but honestly I am not 100% sure what fields are what there, @mitsuhiko pointed me to this settings field)

So to be clear, the only point of the sentry_site field is exactly what you mentioned, if we should store something else in there, happy to be pointed to it!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, organizations will have a couple URLs:

  • sentryUrl which is our root domain and is the same as system.url-prefix. ex. sentry.io
  • organizationUrl which is domain for the organization's UI. ex acme.sentry.io
  • regionUrl Which is the domain that the organization's API endpoints are available on. ex us.sentry.io

Any API requests sent to sentryUrl will be routed to the correct region, but will suffer a latency penalty (because we're proxying the request to the region) and the request will go to the US (this can matter for EU customers).

For this scenario, I think you'll want both the sentryUrl and regionUrl available in the JWT. There are a small number of API endpoints that are only available on sentryUrl (users, integrations, sentry apps).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do I get regionUrl from? (I left a comment in the RFC as well to make sure this is aligned - I'd go with sentry_url and sentry_region_url then)

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project_last_used=HybridCloudForeignKey(
"sentry.Project", null=True, blank=True, on_delete="cascade"
)
project_last_used=BoundedBigIntegerFieldd(db_index=True)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this preferred over the HybridCouldForeignKey? 🤔 Feels like that is designed for exactly this use case, but honestly not entirely sure... 😅

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option would be to use HybridCloudForeignKey("sentry.Project", null=True, blank=True, on_delete="SET_NULL")

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the expertise to judge what is better, since I lack in-depth knowledge of hybrid cloud. @markstory could you give an input here, thanks!

@AniketDas-TekkyAniketDas-Tekky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying my comment from the issue:
#50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 3c20c78 to fcd2cccCompareJune 9, 2023 07:56
@github-actions

github-actionsBot commented Jun 9, 2023

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0483_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"varchar(3000) NOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" varchar_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea

mydea commented Jun 9, 2023

Copy link
Copy Markdown
MemberAuthor

Copying my comment from the issue: #50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

Hey, there is an RFC getsentry/rfcs#91 by @mitsuhiko. If you have any concerns about this on a fundamental level, that's the place to discuss this, I guess. My understanding is that the basic decision that we want to add new org-level tokens has been taken, but @mitsuhiko can probably give you more information if you have more fundamental questions!

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadpyproject.toml Outdated
@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0487_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0488_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea
mydea dismissed AniketDas-Tekky’s stale reviewJune 14, 2023 11:50

I think this has been addressed!

@mydea
mydea merged commit fdc8f5a into masterJun 14, 2023
@mydea
mydea deleted the fn/org-auth-token branch June 14, 2023 12:17
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: BackendAutomatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@mydea@markstory@iambriccardo@mdtro@AniketDas-Tekky@asottile-sentry
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add OrgAuthToken model - #50409

Merged
mydea merged 2 commits into
masterfrom
fn/org-auth-token
Jun 14, 2023
Merged

feat: add OrgAuthToken model#50409
mydea merged 2 commits into
masterfrom
fn/org-auth-token

Conversation

@mydea

@mydeamydea commented Jun 6, 2023

Copy link
Copy Markdown
Member

This adds a new OrgAuthToken model:

  • Tied to an organization
  • Has a token which is expected to be a JWT token
  • Add two basic utilities for token generation/parsing
  • It is possible to assign project(s) to a token

ref #50144

based on RFC getsentry/rfcs#91

@mydeamydea self-assigned this Jun 6, 2023
@mydea
mydea requested a review from a team as a code ownerJune 6, 2023 12:20
@github-actionsgithub-actionsBot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 6, 2023
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure what I should make the max length here. on the RFC there is a comment that we should store this in hashed form, if we do this I guess we need a more generic length here...?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks! I supposed it was for security reasons then it makes sense.

Here we should make the length, the length of the hash. I don't expect we will need to store the token in any other format besides the hashed one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to be explicit that we store token_hashed in the DB.

@codecov

codecovBot commented Jun 6, 2023

Copy link
Copy Markdown

Codecov Report

Merging #50409 (7518568) into master (12d0ad9) will increase coverage by 1.01%.
The diff coverage is 87.87%.

Additional details and impacted files
@@ Coverage Diff @@## master #50409 +/- ##
==========================================
+ Coverage 80.14% 81.15% +1.01% 
==========================================
Files 4843 4845 +2 Lines 203733 203799 +66 Branches 11130 11130 ==========================================
+ Hits 163275 165401 +2126 + Misses 40212 38152 -2060 
Partials 246 246 
Impacted FilesCoverage Δ
src/sentry/models/orgauthtoken.py86.66% <86.66%> (ø)
src/sentry/utils/security/orgauthtoken_jwt.py90.00% <90.00%> (ø)
src/sentry/models/__init__.py100.00% <100.00%> (ø)

... and 113 files with indirect coverage changes

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 69eb8a8 to 5d1c4f2CompareJune 7, 2023 07:41
@mydea

mydea commented Jun 7, 2023

Copy link
Copy Markdown
MemberAuthor

Note: I put these new models into the control silo, mirroring the api token model, I think that is the correct place?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should put this model in orgauthtoken.py since it belongs to the same cluster of models related to org auth tokens.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after talking this through with @mitsuhiko , we decided to skip the projects for now!

@iambriccardoiambriccardoJun 7, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the nature of the relationship, it's not a big deal at all for the db.

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we statically type the list of scopes? Having such a generic field is a bit suspicious.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for reference, this is how the scope list is currently stored in other models. But we may improve on this here :D

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible i would like to bound it list of elements, maybe like:

fromdjango.dbimportmodelsfromdjango.core.exceptionsimportValidationErrorclassMyModel(models.Model):
OPTIONS= [
('option1', 'Option 1'),
('option2', 'Option 2'),
('option3', 'Option 3'),
]
choices_array=models.ArrayField(
models.TextField(),
validators=[],
)
defvalidate_choices_array(value):
forchoiceinvalue:
ifchoicenotindict(MyModel.OPTIONS).keys():
raiseValidationError(f"{choice} is not a valid choice.")
choices_array.validators.append(validate_choices_array)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I implemented this!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_added refers to the User that created this token? If so, I would call this user or added_by.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the idea - I'll rename it to added_by (IMHO user is a potentially confusing as it may imply this token belongs to that user, which it doesn't).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, that's a good point!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a null field, we could have it default to timezone.now and update it when the user uses it, but this changes the semantics. I don't know which ones are more representative of the situation.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it's useful to know that the token was never used, I'd say?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could technically infer it if the two dates are equal but ofc it's not the best way. My main concern was that having nullability in the db is not very nice but I understand your point.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want cascading here? If the last used project is deleted should the org token be deleted?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point @markstory, I missed this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mydea we can just put the project id here as a BoundedBigIntegerField and forget about possible accidental cascading deletions.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point, true! So it should def. not cascade, but should we use HubridCloudForeignKey or BoundedBigIntegerField then? Happy to use BoundedBigIntegerField, just wanting to make sure to get the hybrid could stuff right...!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(BTW also created_by should not cascade but set to null as well, adjusted that too!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be use even when the instance is self-hosted/single-tenant?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, valid question... took this from here: https://github.com/getsentry/rfcs/pull/91/files#diff-3109d1f30b4b81085e85d841deafefb0d1b43cc5345c64514475512a4ce9fdeeR94

IMHO it should be something consistent that says "this is from sentry", and not differ when it is self-hosted/single-tenant. Not sure if sentry.io is the best value for this then, but I'd say it's fine, maybe (just "Sentry" may also be ambiguous, ...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll also need the organizations 'root' URL as in the future, we'll have multiple regions (with different API urls). While customer traffic can continue using https://sentry.io/api/0/** there will be latency overhead (and cost to us) so we should encourage using regional domains instead.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this different from "sentry_site": settings.SENTRY_OPTIONS["system.url-prefix"],? that is what this is designed to do (but honestly I am not 100% sure what fields are what there, @mitsuhiko pointed me to this settings field)

So to be clear, the only point of the sentry_site field is exactly what you mentioned, if we should store something else in there, happy to be pointed to it!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, organizations will have a couple URLs:

  • sentryUrl which is our root domain and is the same as system.url-prefix. ex. sentry.io
  • organizationUrl which is domain for the organization's UI. ex acme.sentry.io
  • regionUrl Which is the domain that the organization's API endpoints are available on. ex us.sentry.io

Any API requests sent to sentryUrl will be routed to the correct region, but will suffer a latency penalty (because we're proxying the request to the region) and the request will go to the US (this can matter for EU customers).

For this scenario, I think you'll want both the sentryUrl and regionUrl available in the JWT. There are a small number of API endpoints that are only available on sentryUrl (users, integrations, sentry apps).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do I get regionUrl from? (I left a comment in the RFC as well to make sure this is aligned - I'd go with sentry_url and sentry_region_url then)

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project_last_used=HybridCloudForeignKey(
"sentry.Project", null=True, blank=True, on_delete="cascade"
)
project_last_used=BoundedBigIntegerFieldd(db_index=True)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this preferred over the HybridCouldForeignKey? 🤔 Feels like that is designed for exactly this use case, but honestly not entirely sure... 😅

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option would be to use HybridCloudForeignKey("sentry.Project", null=True, blank=True, on_delete="SET_NULL")

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the expertise to judge what is better, since I lack in-depth knowledge of hybrid cloud. @markstory could you give an input here, thanks!

@AniketDas-TekkyAniketDas-Tekky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying my comment from the issue:
#50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 3c20c78 to fcd2cccCompareJune 9, 2023 07:56
@github-actions

github-actionsBot commented Jun 9, 2023

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0483_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"varchar(3000) NOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" varchar_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea

mydea commented Jun 9, 2023

Copy link
Copy Markdown
MemberAuthor

Copying my comment from the issue: #50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

Hey, there is an RFC getsentry/rfcs#91 by @mitsuhiko. If you have any concerns about this on a fundamental level, that's the place to discuss this, I guess. My understanding is that the basic decision that we want to add new org-level tokens has been taken, but @mitsuhiko can probably give you more information if you have more fundamental questions!

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadpyproject.toml Outdated
@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0487_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0488_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea
mydea dismissed AniketDas-Tekky’s stale reviewJune 14, 2023 11:50

I think this has been addressed!

@mydea
mydea merged commit fdc8f5a into masterJun 14, 2023
@mydea
mydea deleted the fn/org-auth-token branch June 14, 2023 12:17
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: BackendAutomatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@mydea@markstory@iambriccardo@mdtro@AniketDas-Tekky@asottile-sentry
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: add OrgAuthToken model - #50409

Merged
mydea merged 2 commits into
masterfrom
fn/org-auth-token
Jun 14, 2023
Merged

feat: add OrgAuthToken model#50409
mydea merged 2 commits into
masterfrom
fn/org-auth-token

Conversation

@mydea

@mydeamydea commented Jun 6, 2023

Copy link
Copy Markdown
Member

This adds a new OrgAuthToken model:

  • Tied to an organization
  • Has a token which is expected to be a JWT token
  • Add two basic utilities for token generation/parsing
  • It is possible to assign project(s) to a token

ref #50144

based on RFC getsentry/rfcs#91

@mydeamydea self-assigned this Jun 6, 2023
@mydea
mydea requested a review from a team as a code ownerJune 6, 2023 12:20
@github-actionsgithub-actionsBot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 6, 2023
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure what I should make the max length here. on the RFC there is a comment that we should store this in hashed form, if we do this I guess we need a more generic length here...?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks! I supposed it was for security reasons then it makes sense.

Here we should make the length, the length of the hash. I don't expect we will need to store the token in any other format besides the hashed one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to be explicit that we store token_hashed in the DB.

@codecov

codecovBot commented Jun 6, 2023

Copy link
Copy Markdown

Codecov Report

Merging #50409 (7518568) into master (12d0ad9) will increase coverage by 1.01%.
The diff coverage is 87.87%.

Additional details and impacted files
@@ Coverage Diff @@## master #50409 +/- ##
==========================================
+ Coverage 80.14% 81.15% +1.01% 
==========================================
Files 4843 4845 +2 Lines 203733 203799 +66 Branches 11130 11130 ==========================================
+ Hits 163275 165401 +2126 + Misses 40212 38152 -2060 
Partials 246 246 
Impacted FilesCoverage Δ
src/sentry/models/orgauthtoken.py86.66% <86.66%> (ø)
src/sentry/utils/security/orgauthtoken_jwt.py90.00% <90.00%> (ø)
src/sentry/models/__init__.py100.00% <100.00%> (ø)

... and 113 files with indirect coverage changes

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 69eb8a8 to 5d1c4f2CompareJune 7, 2023 07:41
@mydea

mydea commented Jun 7, 2023

Copy link
Copy Markdown
MemberAuthor

Note: I put these new models into the control silo, mirroring the api token model, I think that is the correct place?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should put this model in orgauthtoken.py since it belongs to the same cluster of models related to org auth tokens.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after talking this through with @mitsuhiko , we decided to skip the projects for now!

@iambriccardoiambriccardoJun 7, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the nature of the relationship, it's not a big deal at all for the db.

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitsuhiko why would we need to store it hashed?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we statically type the list of scopes? Having such a generic field is a bit suspicious.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for reference, this is how the scope list is currently stored in other models. But we may improve on this here :D

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible i would like to bound it list of elements, maybe like:

fromdjango.dbimportmodelsfromdjango.core.exceptionsimportValidationErrorclassMyModel(models.Model):
OPTIONS= [
('option1', 'Option 1'),
('option2', 'Option 2'),
('option3', 'Option 3'),
]
choices_array=models.ArrayField(
models.TextField(),
validators=[],
)
defvalidate_choices_array(value):
forchoiceinvalue:
ifchoicenotindict(MyModel.OPTIONS).keys():
raiseValidationError(f"{choice} is not a valid choice.")
choices_array.validators.append(validate_choices_array)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I implemented this!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_added refers to the User that created this token? If so, I would call this user or added_by.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the idea - I'll rename it to added_by (IMHO user is a potentially confusing as it may imply this token belongs to that user, which it doesn't).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, that's a good point!

Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a null field, we could have it default to timezone.now and update it when the user uses it, but this changes the semantics. I don't know which ones are more representative of the situation.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it's useful to know that the token was never used, I'd say?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could technically infer it if the two dates are equal but ofc it's not the best way. My main concern was that having nullability in the db is not very nice but I understand your point.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/models/orgauthtoken.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning on storing the encoded JWT? or only the important claims within the token?

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want cascading here? If the last used project is deleted should the org token be deleted?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point @markstory, I missed this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mydea we can just put the project id here as a BoundedBigIntegerField and forget about possible accidental cascading deletions.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point, true! So it should def. not cascade, but should we use HubridCloudForeignKey or BoundedBigIntegerField then? Happy to use BoundedBigIntegerField, just wanting to make sure to get the hybrid could stuff right...!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(BTW also created_by should not cascade but set to null as well, adjusted that too!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be use even when the instance is self-hosted/single-tenant?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, valid question... took this from here: https://github.com/getsentry/rfcs/pull/91/files#diff-3109d1f30b4b81085e85d841deafefb0d1b43cc5345c64514475512a4ce9fdeeR94

IMHO it should be something consistent that says "this is from sentry", and not differ when it is self-hosted/single-tenant. Not sure if sentry.io is the best value for this then, but I'd say it's fine, maybe (just "Sentry" may also be ambiguous, ...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll also need the organizations 'root' URL as in the future, we'll have multiple regions (with different API urls). While customer traffic can continue using https://sentry.io/api/0/** there will be latency overhead (and cost to us) so we should encourage using regional domains instead.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this different from "sentry_site": settings.SENTRY_OPTIONS["system.url-prefix"],? that is what this is designed to do (but honestly I am not 100% sure what fields are what there, @mitsuhiko pointed me to this settings field)

So to be clear, the only point of the sentry_site field is exactly what you mentioned, if we should store something else in there, happy to be pointed to it!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, organizations will have a couple URLs:

  • sentryUrl which is our root domain and is the same as system.url-prefix. ex. sentry.io
  • organizationUrl which is domain for the organization's UI. ex acme.sentry.io
  • regionUrl Which is the domain that the organization's API endpoints are available on. ex us.sentry.io

Any API requests sent to sentryUrl will be routed to the correct region, but will suffer a latency penalty (because we're proxying the request to the region) and the request will go to the US (this can matter for EU customers).

For this scenario, I think you'll want both the sentryUrl and regionUrl available in the JWT. There are a small number of API endpoints that are only available on sentryUrl (users, integrations, sentry apps).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do I get regionUrl from? (I left a comment in the RFC as well to make sure this is aligned - I'd go with sentry_url and sentry_region_url then)

Comment threadsrc/sentry/models/orgauthtoken.py Outdated
Comment on lines 42 to 47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project_last_used=HybridCloudForeignKey(
"sentry.Project", null=True, blank=True, on_delete="cascade"
)
project_last_used=BoundedBigIntegerFieldd(db_index=True)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this preferred over the HybridCouldForeignKey? 🤔 Feels like that is designed for exactly this use case, but honestly not entirely sure... 😅

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option would be to use HybridCloudForeignKey("sentry.Project", null=True, blank=True, on_delete="SET_NULL")

@iambriccardoiambriccardoJun 12, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the expertise to judge what is better, since I lack in-depth knowledge of hybrid cloud. @markstory could you give an input here, thanks!

@AniketDas-TekkyAniketDas-Tekky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying my comment from the issue:
#50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

@mydea
mydeaforce-pushed the fn/org-auth-token branch from 3c20c78 to fcd2cccCompareJune 9, 2023 07:56
@github-actions

github-actionsBot commented Jun 9, 2023

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0483_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"varchar(3000) NOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" varchar_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea

mydea commented Jun 9, 2023

Copy link
Copy Markdown
MemberAuthor

Copying my comment from the issue: #50144 (comment)

Basically, is there a tech spec for this? And have we properly investigated what introducing a new token type means?

Hey, there is an RFC getsentry/rfcs#91 by @mitsuhiko. If you have any concerns about this on a fundamental level, that's the place to discuss this, I guess. My understanding is that the basic decision that we want to add new org-level tokens has been taken, but @mitsuhiko can probably give you more information if you have more fundamental questions!

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated
Comment threadsrc/sentry/utils/security/orgauthtoken_jwt.py Outdated

@iambriccardoiambriccardo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR LGTM but I can't judge the usage of the HybridCloudForeignKey. The only thing I can say is that semantically it makes sense.

Comment threadpyproject.toml Outdated
@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0487_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@github-actions

Copy link
Copy Markdown
Contributor

This PR has a migration; here is the generated SQL for src/sentry/migrations/0488_add_orgauthtoken.py ()

---- Create model OrgAuthToken--CREATETABLE "sentry_orgauthtoken" ("id"bigserialNOT NULLPRIMARY KEY, "organization_id"bigintNOT NULL, "token_hashed"textNOT NULL UNIQUE, "token_last_characters"varchar(4) NULL, "name"varchar(255) NOT NULL, "scope_list"text[] NULL, "date_added"timestamp with time zoneNOT NULL, "date_last_used"timestamp with time zoneNULL, "project_last_used_id"bigintNULL, "date_deactivated"timestamp with time zoneNULL, "created_by_id"integerNULL);
ALTERTABLE"sentry_orgauthtoken" ADD CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id"FOREIGN KEY ("created_by_id") REFERENCES"auth_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTERTABLE"sentry_orgauthtoken" VALIDATE CONSTRAINT"sentry_orgauthtoken_created_by_id_5e2288f9_fk_auth_user_id";
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_organization_id_17552a60"ON"sentry_orgauthtoken" ("organization_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_token_hashed_4c36306d_like"ON"sentry_orgauthtoken" ("token_hashed" text_pattern_ops);
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_project_last_used_id_0b9aa639"ON"sentry_orgauthtoken" ("project_last_used_id");
CREATEINDEXCONCURRENTLY"sentry_orgauthtoken_created_by_id_5e2288f9"ON"sentry_orgauthtoken" ("created_by_id");

@mydea
mydea dismissed AniketDas-Tekky’s stale reviewJune 14, 2023 11:50

I think this has been addressed!

@mydea
mydea merged commit fdc8f5a into masterJun 14, 2023
@mydea
mydea deleted the fn/org-auth-token branch June 14, 2023 12:17
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 29, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: BackendAutomatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@mydea@markstory@iambriccardo@mdtro@AniketDas-Tekky@asottile-sentry