1// Copyright 2017 Google LLC.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package internal supports the options and transport packages.
6package internal
7
8import (
9	"crypto/tls"
10	"errors"
11	"net/http"
12
13	"golang.org/x/oauth2"
14	"golang.org/x/oauth2/google"
15	"google.golang.org/api/internal/impersonate"
16	"google.golang.org/grpc"
17)
18
19// DialSettings holds information needed to establish a connection with a
20// Google API service.
21type DialSettings struct {
22	Endpoint            string
23	DefaultEndpoint     string
24	DefaultMTLSEndpoint string
25	Scopes              []string
26	DefaultScopes       []string
27	EnableJwtWithScope  bool
28	TokenSource         oauth2.TokenSource
29	Credentials         *google.Credentials
30	CredentialsFile     string // if set, Token Source is ignored.
31	CredentialsJSON     []byte
32	UserAgent           string
33	APIKey              string
34	Audiences           []string
35	DefaultAudience     string
36	HTTPClient          *http.Client
37	GRPCDialOpts        []grpc.DialOption
38	GRPCConn            *grpc.ClientConn
39	GRPCConnPool        ConnPool
40	GRPCConnPoolSize    int
41	NoAuth              bool
42	TelemetryDisabled   bool
43	ClientCertSource    func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
44	CustomClaims        map[string]interface{}
45	SkipValidation      bool
46	ImpersonationConfig *impersonate.Config
47	EnableDirectPath    bool
48
49	// Google API system parameters. For more information please read:
50	// https://cloud.google.com/apis/docs/system-parameters
51	QuotaProject  string
52	RequestReason string
53}
54
55// GetScopes returns the user-provided scopes, if set, or else falls back to the
56// default scopes.
57func (ds *DialSettings) GetScopes() []string {
58	if len(ds.Scopes) > 0 {
59		return ds.Scopes
60	}
61	return ds.DefaultScopes
62}
63
64// GetAudience returns the user-provided audience, if set, or else falls back to the default audience.
65func (ds *DialSettings) GetAudience() string {
66	if ds.HasCustomAudience() {
67		return ds.Audiences[0]
68	}
69	return ds.DefaultAudience
70}
71
72// HasCustomAudience returns true if a custom audience is provided by users.
73func (ds *DialSettings) HasCustomAudience() bool {
74	return len(ds.Audiences) > 0
75}
76
77// Validate reports an error if ds is invalid.
78func (ds *DialSettings) Validate() error {
79	if ds.SkipValidation {
80		return nil
81	}
82	hasCreds := ds.APIKey != "" || ds.TokenSource != nil || ds.CredentialsFile != "" || ds.Credentials != nil
83	if ds.NoAuth && hasCreds {
84		return errors.New("options.WithoutAuthentication is incompatible with any option that provides credentials")
85	}
86	// Credentials should not appear with other options.
87	// We currently allow TokenSource and CredentialsFile to coexist.
88	// TODO(jba): make TokenSource & CredentialsFile an error (breaking change).
89	nCreds := 0
90	if ds.Credentials != nil {
91		nCreds++
92	}
93	if ds.CredentialsJSON != nil {
94		nCreds++
95	}
96	if ds.CredentialsFile != "" {
97		nCreds++
98	}
99	if ds.APIKey != "" {
100		nCreds++
101	}
102	if ds.TokenSource != nil {
103		nCreds++
104	}
105	if len(ds.Scopes) > 0 && len(ds.Audiences) > 0 {
106		return errors.New("WithScopes is incompatible with WithAudience")
107	}
108	// Accept only one form of credentials, except we allow TokenSource and CredentialsFile for backwards compatibility.
109	if nCreds > 1 && !(nCreds == 2 && ds.TokenSource != nil && ds.CredentialsFile != "") {
110		return errors.New("multiple credential options provided")
111	}
112	if ds.GRPCConn != nil && ds.GRPCConnPool != nil {
113		return errors.New("WithGRPCConn is incompatible with WithConnPool")
114	}
115	if ds.HTTPClient != nil && ds.GRPCConnPool != nil {
116		return errors.New("WithHTTPClient is incompatible with WithConnPool")
117	}
118	if ds.HTTPClient != nil && ds.GRPCConn != nil {
119		return errors.New("WithHTTPClient is incompatible with WithGRPCConn")
120	}
121	if ds.HTTPClient != nil && ds.GRPCDialOpts != nil {
122		return errors.New("WithHTTPClient is incompatible with gRPC dial options")
123	}
124	if ds.HTTPClient != nil && ds.QuotaProject != "" {
125		return errors.New("WithHTTPClient is incompatible with QuotaProject")
126	}
127	if ds.HTTPClient != nil && ds.RequestReason != "" {
128		return errors.New("WithHTTPClient is incompatible with RequestReason")
129	}
130	if ds.HTTPClient != nil && ds.ClientCertSource != nil {
131		return errors.New("WithHTTPClient is incompatible with WithClientCertSource")
132	}
133	if ds.ClientCertSource != nil && (ds.GRPCConn != nil || ds.GRPCConnPool != nil || ds.GRPCConnPoolSize != 0 || ds.GRPCDialOpts != nil) {
134		return errors.New("WithClientCertSource is currently only supported for HTTP. gRPC settings are incompatible")
135	}
136	if ds.ImpersonationConfig != nil && len(ds.ImpersonationConfig.Scopes) == 0 && len(ds.Scopes) == 0 {
137		return errors.New("WithImpersonatedCredentials requires scopes being provided")
138	}
139	return nil
140}
141