-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Token scopes context #1997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Token scopes context #1997
Changes from all commits
beb728a
2022dee
f1d372e
48eb4cb
a209b8e
683641d
f1407b7
2d6e43d
3ce3c3b
adc5c6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ package http | |
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "log/slog" | ||
| "net/http" | ||
|
|
||
|
|
@@ -178,6 +179,14 @@ func withInsiders(next http.Handler) http.Handler { | |
| func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
| inv, err := h.inventoryFactoryFunc(r) | ||
| if err != nil { | ||
| if errors.Is(err, inventory.ErrUnknownTools) { | ||
| w.WriteHeader(http.StatusBadRequest) | ||
| if _, writeErr := w.Write([]byte(err.Error())); writeErr != nil { | ||
| h.logger.Error("failed to write response", "error", writeErr) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| w.WriteHeader(http.StatusInternalServerError) | ||
| return | ||
| } | ||
|
|
@@ -278,8 +287,10 @@ func PATScopeFilter(b *inventory.Builder, r *http.Request, fetcher scopes.Fetche | |
| // Only classic PATs (ghp_ prefix) return OAuth scopes via X-OAuth-Scopes header. | ||
| // Fine-grained PATs and other token types don't support this, so we skip filtering. | ||
| if tokenInfo.TokenType == utils.TokenTypePersonalAccessToken { | ||
| if tokenInfo.ScopesFetched { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just curious, was there a reason we needed this flag at all?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was trying to handle the scenario where we had a token info from copilot API, but it was a token with no scopes. But having it combined with the TokenInfo meant the ordering was a little awkward.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's also implied now, if |
||
| return b.WithFilter(github.CreateToolScopeFilter(tokenInfo.Scopes)) | ||
| // Check if scopes are already in context (should be set by WithPATScopes). If not, fetch them. | ||
| existingScopes, ok := ghcontext.GetTokenScopes(ctx) | ||
| if ok { | ||
| return b.WithFilter(github.CreateToolScopeFilter(existingScopes)) | ||
| } | ||
|
|
||
| scopesList, err := fetcher.FetchTokenScopes(ctx, tokenInfo.Token) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,17 +94,19 @@ func WithScopeChallenge(oauthCfg *oauth.Config, scopeFetcher scopes.FetcherInter | |
| return | ||
| } | ||
|
|
||
| // Get OAuth scopes from GitHub API | ||
| activeScopes, err := scopeFetcher.FetchTokenScopes(ctx, tokenInfo.Token) | ||
| if err != nil { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| // Get OAuth scopes for Token. First check if scopes are already in context, then fetch from GitHub if not present. | ||
| // This allows Remote Server to pass scope info to avoid redundant GitHub API calls. | ||
| activeScopes, ok := ghcontext.GetTokenScopes(ctx) | ||
| if !ok || (len(activeScopes) == 0 && tokenInfo.Token != "") { | ||
| activeScopes, err = scopeFetcher.FetchTokenScopes(ctx, tokenInfo.Token) | ||
| if err != nil { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
| } | ||
|
|
||
| // Store active scopes in context for downstream use | ||
| tokenInfo.Scopes = activeScopes | ||
| tokenInfo.ScopesFetched = true | ||
| ctx = ghcontext.WithTokenInfo(ctx, tokenInfo) | ||
| ctx = ghcontext.WithTokenScopes(ctx, activeScopes) | ||
| r = r.WithContext(ctx) | ||
|
Comment on lines
97
to
110
|
||
|
|
||
| // Check if user has the required scopes | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,16 @@ import ( | |
| func ExtractUserToken(oauthCfg *oauth.Config) func(next http.Handler) http.Handler { | ||
| return func(next http.Handler) http.Handler { | ||
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| ctx := r.Context() | ||
|
|
||
| // Check if token info already exists in context, if it does, skip extraction. | ||
| // In remote setup, we may have already extracted token info earlier. | ||
| if _, ok := ghcontext.GetTokenInfo(ctx); ok { | ||
| // Token info already exists in context, skip extraction | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
Comment on lines
+18
to
+24
|
||
|
|
||
| tokenType, token, err := utils.ParseAuthorizationHeader(r) | ||
| if err != nil { | ||
| // For missing Authorization header, return 401 with WWW-Authenticate header per MCP spec | ||
|
|
@@ -25,7 +35,6 @@ func ExtractUserToken(oauthCfg *oauth.Config) func(next http.Handler) http.Handl | |
| return | ||
| } | ||
|
|
||
| ctx := r.Context() | ||
| ctx = ghcontext.WithTokenInfo(ctx, &ghcontext.TokenInfo{ | ||
| Token: token, | ||
| TokenType: tokenType, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.