go/adbc/driver/flightsql/flightsql_oauth.go (108 lines of code) (raw):

// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package flightsql import ( "context" "fmt" "golang.org/x/oauth2" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/oauth" ) const ( ClientCredentials = "client_credentials" TokenExchange = "token_exchange" ) type oAuthOption struct { isRequired bool oAuthKey string } var ( clientCredentialsParams = map[string]oAuthOption{ OptionKeyClientId: {true, "client_id"}, OptionKeyClientSecret: {true, "client_secret"}, OptionKeyTokenURI: {true, "token_uri"}, OptionKeyScope: {false, "scope"}, } tokenExchangParams = map[string]oAuthOption{ OptionKeySubjectToken: {true, "subject_token"}, OptionKeySubjectTokenType: {true, "subject_token_type"}, OptionKeyReqTokenType: {false, "requested_token_type"}, OptionKeyExchangeAud: {false, "audience"}, OptionKeyExchangeResource: {false, "resource"}, OptionKeyExchangeScope: {false, "scope"}, } ) func parseOAuthOptions(options map[string]string, paramMap map[string]oAuthOption, flowName string) (map[string]string, error) { params := map[string]string{} for key, param := range paramMap { if value, ok := options[key]; ok { params[key] = value delete(options, key) } else if param.isRequired { return nil, fmt.Errorf("%s grant requires %s", flowName, key) } } return params, nil } func exchangeToken(conf *oauth2.Config, codeOptions []oauth2.AuthCodeOption) (credentials.PerRPCCredentials, error) { ctx := context.Background() tok, err := conf.Exchange(ctx, "", codeOptions...) if err != nil { return nil, err } return &oauth.TokenSource{TokenSource: conf.TokenSource(ctx, tok)}, nil } func newClientCredentials(options map[string]string) (credentials.PerRPCCredentials, error) { codeOptions := []oauth2.AuthCodeOption{ // Required value for client credentials requests as specified in https://datatracker.ietf.org/doc/html/rfc6749#section-4.4.2 oauth2.SetAuthURLParam("grant_type", "client_credentials"), } params, err := parseOAuthOptions(options, clientCredentialsParams, "client credentials") if err != nil { return nil, err } conf := &oauth2.Config{ ClientID: params[OptionKeyClientId], ClientSecret: params[OptionKeyClientSecret], Endpoint: oauth2.Endpoint{ TokenURL: params[OptionKeyTokenURI], }, } if scopes, ok := params[OptionKeyScope]; ok { conf.Scopes = []string{scopes} } return exchangeToken(conf, codeOptions) } func newTokenExchangeFlow(options map[string]string) (credentials.PerRPCCredentials, error) { tokenURI, ok := options[OptionKeyTokenURI] if !ok { return nil, fmt.Errorf("token exchange grant requires %s", OptionKeyTokenURI) } delete(options, OptionKeyTokenURI) conf := &oauth2.Config{ Endpoint: oauth2.Endpoint{ TokenURL: tokenURI, }, } codeOptions := []oauth2.AuthCodeOption{ // Required value for token exchange requests as specified in https://datatracker.ietf.org/doc/html/rfc8693#name-request oauth2.SetAuthURLParam("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange"), } params, err := parseOAuthOptions(options, tokenExchangParams, "token exchange") if err != nil { return nil, err } for key, param := range tokenExchangParams { if value, ok := params[key]; ok { codeOptions = append(codeOptions, oauth2.SetAuthURLParam(param.oAuthKey, value)) } } // actor token and actor token type are optional // but if one is present, the other must be present if actor, ok := options[OptionKeyActorToken]; ok { codeOptions = append(codeOptions, oauth2.SetAuthURLParam("actor_token", actor)) delete(options, OptionKeyActorToken) if actorTokenType, ok := options[OptionKeyActorTokenType]; ok { codeOptions = append(codeOptions, oauth2.SetAuthURLParam("actor_token_type", actorTokenType)) delete(options, OptionKeyActorTokenType) } else { return nil, fmt.Errorf("token exchange grant requires %s when %s is provided", OptionKeyActorTokenType, OptionKeyActorToken) } } return exchangeToken(conf, codeOptions) }