github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/credentials/oauth/oauth.go (about) 1 /* 2 * 3 * Copyright 2015 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Package oauth implements gRPC credentials using OAuth. 20 package oauth 21 22 import ( 23 "context" 24 "fmt" 25 "io/ioutil" 26 "net/url" 27 "sync" 28 29 "github.com/hxx258456/ccgo/grpc/credentials" 30 "golang.org/x/oauth2" 31 "golang.org/x/oauth2/google" 32 "golang.org/x/oauth2/jwt" 33 ) 34 35 // TokenSource supplies PerRPCCredentials from an oauth2.TokenSource. 36 type TokenSource struct { 37 oauth2.TokenSource 38 } 39 40 // GetRequestMetadata gets the request metadata as a map from a TokenSource. 41 func (ts TokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 42 token, err := ts.Token() 43 if err != nil { 44 return nil, err 45 } 46 ri, _ := credentials.RequestInfoFromContext(ctx) 47 if err = credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil { 48 return nil, fmt.Errorf("unable to transfer TokenSource PerRPCCredentials: %v", err) 49 } 50 return map[string]string{ 51 "authorization": token.Type() + " " + token.AccessToken, 52 }, nil 53 } 54 55 // RequireTransportSecurity indicates whether the credentials requires transport security. 56 func (ts TokenSource) RequireTransportSecurity() bool { 57 return true 58 } 59 60 // removeServiceNameFromJWTURI removes RPC service name from URI. 61 func removeServiceNameFromJWTURI(uri string) (string, error) { 62 parsed, err := url.Parse(uri) 63 if err != nil { 64 return "", err 65 } 66 parsed.Path = "/" 67 return parsed.String(), nil 68 } 69 70 type jwtAccess struct { 71 jsonKey []byte 72 } 73 74 // NewJWTAccessFromFile creates PerRPCCredentials from the given keyFile. 75 func NewJWTAccessFromFile(keyFile string) (credentials.PerRPCCredentials, error) { 76 jsonKey, err := ioutil.ReadFile(keyFile) 77 if err != nil { 78 return nil, fmt.Errorf("credentials: failed to read the service account key file: %v", err) 79 } 80 return NewJWTAccessFromKey(jsonKey) 81 } 82 83 // NewJWTAccessFromKey creates PerRPCCredentials from the given jsonKey. 84 func NewJWTAccessFromKey(jsonKey []byte) (credentials.PerRPCCredentials, error) { 85 return jwtAccess{jsonKey}, nil 86 } 87 88 func (j jwtAccess) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 89 // Remove RPC service name from URI that will be used as audience 90 // in a self-signed JWT token. It follows https://google.aip.dev/auth/4111. 91 aud, err := removeServiceNameFromJWTURI(uri[0]) 92 if err != nil { 93 return nil, err 94 } 95 // TODO: the returned TokenSource is reusable. Store it in a sync.Map, with 96 // uri as the key, to avoid recreating for every RPC. 97 ts, err := google.JWTAccessTokenSourceFromJSON(j.jsonKey, aud) 98 if err != nil { 99 return nil, err 100 } 101 token, err := ts.Token() 102 if err != nil { 103 return nil, err 104 } 105 ri, _ := credentials.RequestInfoFromContext(ctx) 106 if err = credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil { 107 return nil, fmt.Errorf("unable to transfer jwtAccess PerRPCCredentials: %v", err) 108 } 109 return map[string]string{ 110 "authorization": token.Type() + " " + token.AccessToken, 111 }, nil 112 } 113 114 func (j jwtAccess) RequireTransportSecurity() bool { 115 return true 116 } 117 118 // oauthAccess supplies PerRPCCredentials from a given token. 119 type oauthAccess struct { 120 token oauth2.Token 121 } 122 123 // NewOauthAccess constructs the PerRPCCredentials using a given token. 124 func NewOauthAccess(token *oauth2.Token) credentials.PerRPCCredentials { 125 return oauthAccess{token: *token} 126 } 127 128 func (oa oauthAccess) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 129 ri, _ := credentials.RequestInfoFromContext(ctx) 130 if err := credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil { 131 return nil, fmt.Errorf("unable to transfer oauthAccess PerRPCCredentials: %v", err) 132 } 133 return map[string]string{ 134 "authorization": oa.token.Type() + " " + oa.token.AccessToken, 135 }, nil 136 } 137 138 func (oa oauthAccess) RequireTransportSecurity() bool { 139 return true 140 } 141 142 // NewComputeEngine constructs the PerRPCCredentials that fetches access tokens from 143 // Google Compute Engine (GCE)'s metadata server. It is only valid to use this 144 // if your program is running on a GCE instance. 145 // TODO(dsymonds): Deprecate and remove this. 146 func NewComputeEngine() credentials.PerRPCCredentials { 147 return TokenSource{google.ComputeTokenSource("")} 148 } 149 150 // serviceAccount represents PerRPCCredentials via JWT signing key. 151 type serviceAccount struct { 152 mu sync.Mutex 153 config *jwt.Config 154 t *oauth2.Token 155 } 156 157 func (s *serviceAccount) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 158 s.mu.Lock() 159 defer s.mu.Unlock() 160 if !s.t.Valid() { 161 var err error 162 s.t, err = s.config.TokenSource(ctx).Token() 163 if err != nil { 164 return nil, err 165 } 166 } 167 ri, _ := credentials.RequestInfoFromContext(ctx) 168 if err := credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil { 169 return nil, fmt.Errorf("unable to transfer serviceAccount PerRPCCredentials: %v", err) 170 } 171 return map[string]string{ 172 "authorization": s.t.Type() + " " + s.t.AccessToken, 173 }, nil 174 } 175 176 func (s *serviceAccount) RequireTransportSecurity() bool { 177 return true 178 } 179 180 // NewServiceAccountFromKey constructs the PerRPCCredentials using the JSON key slice 181 // from a Google Developers service account. 182 func NewServiceAccountFromKey(jsonKey []byte, scope ...string) (credentials.PerRPCCredentials, error) { 183 config, err := google.JWTConfigFromJSON(jsonKey, scope...) 184 if err != nil { 185 return nil, err 186 } 187 return &serviceAccount{config: config}, nil 188 } 189 190 // NewServiceAccountFromFile constructs the PerRPCCredentials using the JSON key file 191 // of a Google Developers service account. 192 func NewServiceAccountFromFile(keyFile string, scope ...string) (credentials.PerRPCCredentials, error) { 193 jsonKey, err := ioutil.ReadFile(keyFile) 194 if err != nil { 195 return nil, fmt.Errorf("credentials: failed to read the service account key file: %v", err) 196 } 197 return NewServiceAccountFromKey(jsonKey, scope...) 198 } 199 200 // NewApplicationDefault returns "Application Default Credentials". For more 201 // detail, see https://developers.google.com/accounts/docs/application-default-credentials. 202 func NewApplicationDefault(ctx context.Context, scope ...string) (credentials.PerRPCCredentials, error) { 203 creds, err := google.FindDefaultCredentials(ctx, scope...) 204 if err != nil { 205 return nil, err 206 } 207 208 // If JSON is nil, the authentication is provided by the environment and not 209 // with a credentials file, e.g. when code is running on Google Cloud 210 // Platform. Use the returned token source. 211 if creds.JSON == nil { 212 return TokenSource{creds.TokenSource}, nil 213 } 214 215 // If auth is provided by env variable or creds file, the behavior will be 216 // different based on whether scope is set. Because the returned 217 // creds.TokenSource does oauth with jwt by default, and it requires scope. 218 // We can only use it if scope is not empty, otherwise it will fail with 219 // missing scope error. 220 // 221 // If scope is set, use it, it should just work. 222 // 223 // If scope is not set, we try to use jwt directly without oauth (this only 224 // works if it's a service account). 225 226 if len(scope) != 0 { 227 return TokenSource{creds.TokenSource}, nil 228 } 229 230 // Try to convert JSON to a jwt config without setting the optional scope 231 // parameter to check if it's a service account (the function errors if it's 232 // not). This is necessary because the returned config doesn't show the type 233 // of the account. 234 if _, err := google.JWTConfigFromJSON(creds.JSON); err != nil { 235 // If this fails, it's not a service account, return the original 236 // TokenSource from above. 237 return TokenSource{creds.TokenSource}, nil 238 } 239 240 // If it's a service account, create a JWT only access with the key. 241 return NewJWTAccessFromKey(creds.JSON) 242 }