go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/gerrit/prod.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package gerrit 16 17 import ( 18 "context" 19 "net/http" 20 "strings" 21 "time" 22 23 "golang.org/x/oauth2" 24 25 luciauth "go.chromium.org/luci/auth" 26 "go.chromium.org/luci/common/api/gerrit" 27 "go.chromium.org/luci/common/errors" 28 "go.chromium.org/luci/server/auth" 29 ) 30 31 // prodFactory knows how to construct Gerrit clients and hop over Gerrit 32 // mirrors. 33 type prodFactory struct { 34 baseTransport http.RoundTripper 35 36 mirrorHostPrefixes []string 37 38 mockMintProjectToken func(context.Context, auth.ProjectTokenParams) (*auth.Token, error) 39 } 40 41 var errEmptyProjectToken = errors.New("crbug/824492: Project token is empty") 42 43 func newProd(ctx context.Context, mirrorHostPrefixes ...string) (*prodFactory, error) { 44 t, err := auth.GetRPCTransport(ctx, auth.NoAuth) 45 if err != nil { 46 return nil, err 47 } 48 return &prodFactory{ 49 baseTransport: t, 50 mirrorHostPrefixes: mirrorHostPrefixes, 51 }, nil 52 } 53 54 // MakeMirrorIterator implements Factory. 55 func (p *prodFactory) MakeMirrorIterator(ctx context.Context) *MirrorIterator { 56 return newMirrorIterator(ctx, p.mirrorHostPrefixes...) 57 } 58 59 // MakeClient implements Factory. 60 func (f *prodFactory) MakeClient(ctx context.Context, gerritHost, luciProject string) (Client, error) { 61 if strings.ContainsRune(luciProject, '.') { 62 panic(errors.Reason("swapped host %q with luciProject %q", gerritHost, luciProject).Err()) 63 } 64 // TODO(crbug/824492): use auth.GetRPCTransport(ctx, auth.AsProject, ...) 65 // directly after pssa migration is over. Currently, we need a special 66 // error to detect whether pssa is configured or not. 67 t, err := f.transport(gerritHost, luciProject) 68 if err != nil { 69 return nil, err 70 } 71 return gerrit.NewRESTClient(&http.Client{Transport: t}, gerritHost, true) 72 } 73 74 func (f *prodFactory) transport(gerritHost, luciProject string) (http.RoundTripper, error) { 75 return luciauth.NewModifyingTransport(f.baseTransport, func(req *http.Request) error { 76 tok, err := f.token(req.Context(), gerritHost, luciProject) 77 if err != nil { 78 return err 79 } 80 req.Header.Set("Authorization", tok.TokenType+" "+tok.AccessToken) 81 return nil 82 }), nil 83 } 84 85 func (f *prodFactory) token(ctx context.Context, gerritHost, luciProject string) (*oauth2.Token, error) { 86 req := auth.ProjectTokenParams{ 87 MinTTL: 2 * time.Minute, 88 LuciProject: luciProject, 89 OAuthScopes: []string{gerrit.OAuthScope}, 90 } 91 mintToken := auth.MintProjectToken 92 if f.mockMintProjectToken != nil { 93 mintToken = f.mockMintProjectToken 94 } 95 switch token, err := mintToken(ctx, req); { 96 case err != nil: 97 return nil, err 98 case token == nil: 99 return nil, errors.Annotate(errEmptyProjectToken, "LUCI project: %q", luciProject).Err() 100 default: 101 return &oauth2.Token{ 102 AccessToken: token.Token, 103 TokenType: "Bearer", 104 }, nil 105 } 106 }