Home | History | Annotate | Download | only in internal
      1 // Copyright 2017 Google Inc. All Rights Reserved.
      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 internal
     16 
     17 import (
     18 	"encoding/json"
     19 	"fmt"
     20 	"io/ioutil"
     21 	"time"
     22 
     23 	"golang.org/x/net/context"
     24 	"golang.org/x/oauth2"
     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.
     30 func Creds(ctx context.Context, ds *DialSettings) (*google.DefaultCredentials, error) {
     31 	if ds.CredentialsFile != "" {
     32 		return credFileTokenSource(ctx, ds.CredentialsFile, ds.Scopes...)
     33 	}
     34 	if ds.TokenSource != nil {
     35 		return &google.DefaultCredentials{TokenSource: ds.TokenSource}, nil
     36 	}
     37 	return google.FindDefaultCredentials(ctx, ds.Scopes...)
     38 }
     39 
     40 // credFileTokenSource reads a refresh token file or a service account and returns
     41 // a TokenSource constructed from the config.
     42 func credFileTokenSource(ctx context.Context, filename string, scope ...string) (*google.DefaultCredentials, error) {
     43 	data, err := ioutil.ReadFile(filename)
     44 	if err != nil {
     45 		return nil, fmt.Errorf("cannot read credentials file: %v", err)
     46 	}
     47 	// See if it is a refresh token credentials file first.
     48 	ts, ok, err := refreshTokenTokenSource(ctx, data, scope...)
     49 	if err != nil {
     50 		return nil, err
     51 	}
     52 	if ok {
     53 		return &google.DefaultCredentials{
     54 			TokenSource: ts,
     55 			JSON:        data,
     56 		}, nil
     57 	}
     58 
     59 	// If not, it should be a service account.
     60 	cfg, err := google.JWTConfigFromJSON(data, scope...)
     61 	if err != nil {
     62 		return nil, fmt.Errorf("google.JWTConfigFromJSON: %v", err)
     63 	}
     64 	// jwt.Config does not expose the project ID, so re-unmarshal to get it.
     65 	var pid struct {
     66 		ProjectID string `json:"project_id"`
     67 	}
     68 	if err := json.Unmarshal(data, &pid); err != nil {
     69 		return nil, err
     70 	}
     71 	return &google.DefaultCredentials{
     72 		ProjectID:   pid.ProjectID,
     73 		TokenSource: cfg.TokenSource(ctx),
     74 		JSON:        data,
     75 	}, nil
     76 }
     77 
     78 func refreshTokenTokenSource(ctx context.Context, data []byte, scope ...string) (oauth2.TokenSource, bool, error) {
     79 	var c cred
     80 	if err := json.Unmarshal(data, &c); err != nil {
     81 		return nil, false, fmt.Errorf("cannot unmarshal credentials file: %v", err)
     82 	}
     83 	if c.ClientID == "" || c.ClientSecret == "" || c.RefreshToken == "" || c.Type != "authorized_user" {
     84 		return nil, false, nil
     85 	}
     86 	cfg := &oauth2.Config{
     87 		ClientID:     c.ClientID,
     88 		ClientSecret: c.ClientSecret,
     89 		Endpoint:     google.Endpoint,
     90 		RedirectURL:  "urn:ietf:wg:oauth:2.0:oob",
     91 		Scopes:       scope,
     92 	}
     93 	return cfg.TokenSource(ctx, &oauth2.Token{
     94 		RefreshToken: c.RefreshToken,
     95 		Expiry:       time.Now(),
     96 	}), true, nil
     97 }
     98 
     99 type cred struct {
    100 	ClientID     string `json:"client_id"`
    101 	ClientSecret string `json:"client_secret"`
    102 	RefreshToken string `json:"refresh_token"`
    103 	Type         string `json:"type"`
    104 }
    105