1// Copyright 2017 Google LLC 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 15package internal 16 17import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "io/ioutil" 22 23 "golang.org/x/oauth2" 24 25 "golang.org/x/oauth2/google" 26) 27 28// Creds returns credential information obtained from DialSettings, or if none, then 29// it returns default credential information. 30func Creds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) { 31 if ds.Credentials != nil { 32 return ds.Credentials, nil 33 } 34 if ds.CredentialsJSON != nil { 35 return credentialsFromJSON(ctx, ds.CredentialsJSON, ds.Endpoint, ds.Scopes, ds.Audiences) 36 } 37 if ds.CredentialsFile != "" { 38 data, err := ioutil.ReadFile(ds.CredentialsFile) 39 if err != nil { 40 return nil, fmt.Errorf("cannot read credentials file: %v", err) 41 } 42 return credentialsFromJSON(ctx, data, ds.Endpoint, ds.Scopes, ds.Audiences) 43 } 44 if ds.TokenSource != nil { 45 return &google.Credentials{TokenSource: ds.TokenSource}, nil 46 } 47 cred, err := google.FindDefaultCredentials(ctx, ds.Scopes...) 48 if err != nil { 49 return nil, err 50 } 51 if len(cred.JSON) > 0 { 52 return credentialsFromJSON(ctx, cred.JSON, ds.Endpoint, ds.Scopes, ds.Audiences) 53 } 54 // For GAE and GCE, the JSON is empty so return the default credentials directly. 55 return cred, nil 56} 57 58// JSON key file type. 59const ( 60 serviceAccountKey = "service_account" 61) 62 63// credentialsFromJSON returns a google.Credentials based on the input. 64// 65// - If the JSON is a service account and no scopes provided, returns self-signed JWT auth flow 66// - Otherwise, returns OAuth 2.0 flow. 67func credentialsFromJSON(ctx context.Context, data []byte, endpoint string, scopes []string, audiences []string) (*google.Credentials, error) { 68 cred, err := google.CredentialsFromJSON(ctx, data, scopes...) 69 if err != nil { 70 return nil, err 71 } 72 if len(data) > 0 && len(scopes) == 0 { 73 var f struct { 74 Type string `json:"type"` 75 // The rest JSON fields are omitted because they are not used. 76 } 77 if err := json.Unmarshal(cred.JSON, &f); err != nil { 78 return nil, err 79 } 80 if f.Type == serviceAccountKey { 81 ts, err := selfSignedJWTTokenSource(data, endpoint, audiences) 82 if err != nil { 83 return nil, err 84 } 85 cred.TokenSource = ts 86 } 87 } 88 return cred, err 89} 90 91func selfSignedJWTTokenSource(data []byte, endpoint string, audiences []string) (oauth2.TokenSource, error) { 92 // Use the API endpoint as the default audience 93 audience := endpoint 94 if len(audiences) > 0 { 95 // TODO(shinfan): Update golang oauth to support multiple audiences. 96 if len(audiences) > 1 { 97 return nil, fmt.Errorf("multiple audiences support is not implemented") 98 } 99 audience = audiences[0] 100 } 101 return google.JWTAccessTokenSourceFromJSON(data, audience) 102} 103