go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/gcloud/googleoauth/info.go (about) 1 // Copyright 2017 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 googleoauth 16 17 import ( 18 "context" 19 "encoding/json" 20 "net/http" 21 "net/url" 22 23 "go.chromium.org/luci/common/errors" 24 "go.chromium.org/luci/common/logging" 25 "go.chromium.org/luci/common/retry/transient" 26 "google.golang.org/api/googleapi" 27 28 "golang.org/x/net/context/ctxhttp" 29 ) 30 31 const ( 32 // TokeninfoEndpoint is Google's token info endpoint. 33 TokeninfoEndpoint = "https://oauth2.googleapis.com/tokeninfo" 34 ) 35 36 // ErrBadToken is returned by GetTokenInfo if the passed token is invalid. 37 var ErrBadToken = errors.New("bad token") 38 39 // TokenInfoParams are parameters for GetTokenInfo call. 40 type TokenInfoParams struct { 41 AccessToken string // an access token to check 42 IDToken string // an ID token to check (overrides AccessToken) 43 44 Client *http.Client // non-authenticating client to use for the call 45 Endpoint string // an endpoint to use instead of the default one 46 } 47 48 // TokenInfo is information about an access or ID tokens. 49 // 50 // Of primary importance are 'email', 'email_verified', 'scope' and 'aud' 51 // fields. If the caller using token info endpoint to validate tokens, it MUST 52 // check correctness of these fields. 53 type TokenInfo struct { 54 Azp string `json:"azp"` 55 Aud string `json:"aud"` 56 Sub string `json:"sub"` 57 Scope string `json:"scope"` 58 Exp int64 `json:"exp,string"` 59 ExpiresIn int64 `json:"expires_in,string"` 60 Email string `json:"email"` 61 EmailVerified bool `json:"email_verified,string"` 62 AccessType string `json:"access_type"` 63 } 64 65 // GetTokenInfo queries token info endpoint and returns information about 66 // the token if it is recognized. 67 // 68 // See https://developers.google.com/identity/sign-in/android/backend-auth#calling-the-tokeninfo-endpoint. 69 // 70 // On invalid token (as indicated by 4** HTTP response) returns ErrBadToken. On 71 // other HTTP-level errors (e.g. HTTP 500) returns transient-wrapped 72 // *googleapi.Error. On network-level errors returns them in a transient 73 // wrapper. 74 func GetTokenInfo(ctx context.Context, params TokenInfoParams) (*TokenInfo, error) { 75 if params.Client == nil { 76 params.Client = http.DefaultClient 77 } 78 if params.Endpoint == "" { 79 params.Endpoint = TokeninfoEndpoint 80 } 81 82 // Note: we must not log full URL of this call, it contains sensitive info. 83 v := url.Values{} 84 if params.IDToken != "" { 85 v.Add("id_token", params.IDToken) 86 } else { 87 v.Add("access_token", params.AccessToken) 88 } 89 resp, err := ctxhttp.Get(ctx, params.Client, params.Endpoint+"?"+v.Encode()) 90 if err != nil { 91 return nil, transient.Tag.Apply(err) 92 } 93 defer googleapi.CloseBody(resp) 94 if err := googleapi.CheckResponse(resp); err != nil { 95 if apiErr, ok := err.(*googleapi.Error); ok && apiErr.Code < 500 { 96 return nil, ErrBadToken 97 } 98 return nil, transient.Tag.Apply(err) 99 } 100 101 info := &TokenInfo{} 102 if err := json.NewDecoder(resp.Body).Decode(info); err != nil { 103 // This should never happen. If it does, the token endpoint has gone mad, 104 // and maybe it will recover soon. So mark the error as transient. 105 logging.WithError(err).Errorf(ctx, "Bad token info endpoint response") 106 return nil, transient.Tag.Apply(err) 107 } 108 109 return info, nil 110 }