| // 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 oauth2 |
| |
| import ( |
| "net/http" |
| |
| "github.com/apache/pulsar-client-go/oauth2/clock" |
| |
| "github.com/pkg/errors" |
| ) |
| |
| // ClientCredentialsFlow takes care of the mechanics needed for getting an access |
| // token using the OAuth 2.0 "Client Credentials Flow" |
| type ClientCredentialsFlow struct { |
| options ClientCredentialsFlowOptions |
| oidcWellKnownEndpoints OIDCWellKnownEndpoints |
| keyfile *KeyFile |
| exchanger ClientCredentialsExchanger |
| clock clock.Clock |
| } |
| |
| // ClientCredentialsProvider abstracts getting client credentials |
| type ClientCredentialsProvider interface { |
| GetClientCredentials() (*KeyFile, error) |
| } |
| |
| // ClientCredentialsExchanger abstracts exchanging client credentials for tokens |
| type ClientCredentialsExchanger interface { |
| ExchangeClientCredentials(req ClientCredentialsExchangeRequest) (*TokenResult, error) |
| } |
| |
| type ClientCredentialsFlowOptions struct { |
| KeyFile string |
| AdditionalScopes []string |
| } |
| |
| func newClientCredentialsFlow( |
| options ClientCredentialsFlowOptions, |
| keyfile *KeyFile, |
| oidcWellKnownEndpoints OIDCWellKnownEndpoints, |
| exchanger ClientCredentialsExchanger, |
| clock clock.Clock) *ClientCredentialsFlow { |
| return &ClientCredentialsFlow{ |
| options: options, |
| oidcWellKnownEndpoints: oidcWellKnownEndpoints, |
| keyfile: keyfile, |
| exchanger: exchanger, |
| clock: clock, |
| } |
| } |
| |
| // NewDefaultClientCredentialsFlow provides an easy way to build up a default |
| // client credentials flow with all the correct configuration. |
| func NewDefaultClientCredentialsFlow(options ClientCredentialsFlowOptions) (*ClientCredentialsFlow, error) { |
| |
| credsProvider := NewClientCredentialsProviderFromKeyFile(options.KeyFile) |
| keyFile, err := credsProvider.GetClientCredentials() |
| if err != nil { |
| return nil, errors.Wrap(err, "could not get client credentials") |
| } |
| |
| wellKnownEndpoints, err := GetOIDCWellKnownEndpointsFromIssuerURL(keyFile.IssuerURL) |
| if err != nil { |
| return nil, err |
| } |
| |
| tokenRetriever := NewTokenRetriever(&http.Client{}) |
| |
| return newClientCredentialsFlow( |
| options, |
| keyFile, |
| *wellKnownEndpoints, |
| tokenRetriever, |
| clock.RealClock{}), nil |
| } |
| |
| var _ Flow = &ClientCredentialsFlow{} |
| |
| func (c *ClientCredentialsFlow) Authorize(audience string) (*AuthorizationGrant, error) { |
| var err error |
| grant := &AuthorizationGrant{ |
| Type: GrantTypeClientCredentials, |
| Audience: audience, |
| ClientID: c.keyfile.ClientID, |
| ClientCredentials: c.keyfile, |
| TokenEndpoint: c.oidcWellKnownEndpoints.TokenEndpoint, |
| } |
| |
| // test the credentials and obtain an initial access token |
| refresher := &ClientCredentialsGrantRefresher{ |
| exchanger: c.exchanger, |
| clock: c.clock, |
| } |
| grant, err = refresher.Refresh(grant) |
| if err != nil { |
| return nil, errors.Wrap(err, "authentication failed using client credentials") |
| } |
| return grant, nil |
| } |
| |
| type ClientCredentialsGrantRefresher struct { |
| exchanger ClientCredentialsExchanger |
| clock clock.Clock |
| } |
| |
| func NewDefaultClientCredentialsGrantRefresher(clock clock.Clock) (*ClientCredentialsGrantRefresher, error) { |
| tokenRetriever := NewTokenRetriever(&http.Client{}) |
| return &ClientCredentialsGrantRefresher{ |
| exchanger: tokenRetriever, |
| clock: clock, |
| }, nil |
| } |
| |
| var _ AuthorizationGrantRefresher = &ClientCredentialsGrantRefresher{} |
| |
| func (g *ClientCredentialsGrantRefresher) Refresh(grant *AuthorizationGrant) (*AuthorizationGrant, error) { |
| if grant.Type != GrantTypeClientCredentials { |
| return nil, errors.New("unsupported grant type") |
| } |
| |
| exchangeRequest := ClientCredentialsExchangeRequest{ |
| TokenEndpoint: grant.TokenEndpoint, |
| Audience: grant.Audience, |
| ClientID: grant.ClientCredentials.ClientID, |
| ClientSecret: grant.ClientCredentials.ClientSecret, |
| } |
| tr, err := g.exchanger.ExchangeClientCredentials(exchangeRequest) |
| if err != nil { |
| return nil, errors.Wrap(err, "could not exchange client credentials") |
| } |
| |
| token := convertToOAuth2Token(tr, g.clock) |
| grant = &AuthorizationGrant{ |
| Type: GrantTypeClientCredentials, |
| Audience: grant.Audience, |
| ClientID: grant.ClientID, |
| ClientCredentials: grant.ClientCredentials, |
| TokenEndpoint: grant.TokenEndpoint, |
| Token: &token, |
| } |
| return grant, nil |
| } |