/* Copyright 2020, 2021 Jan Dittberner Licensed 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 services import ( "bytes" "context" "encoding/json" "net/http" "net/url" "github.com/lestrrat-go/jwx/jwk" log "github.com/sirupsen/logrus" "golang.org/x/oauth2" "git.cacert.org/oidc_login/common/models" ) type oidcContextKey int // context keys const ( ctxOidcConfig oidcContextKey = iota ctxOAuth2Config ctxOidcJwks ) // Parameters for DiscoverOIDC type OidcParams struct { OidcServer string OidcClientId string OidcClientSecret string APIClient *http.Client } // Discover OpenID Connect parameters from the discovery endpoint and the // JSON Web Key Set from the discovered jwksUri. // // The subset of values specified by models.OpenIDConfiguration is stored in // the given context and can be retrieved from the context by GetOidcConfig. // // OAuth2 specific values are stored in another context object and can be // retrieved by GetOAuth2Config. // // The JSON Web Key Set can be retrieved by GetJwkSet. func DiscoverOIDC(ctx context.Context, logger *log.Logger, params *OidcParams) (context.Context, error) { var discoveryUrl *url.URL discoveryUrl, err := url.Parse(params.OidcServer) if err != nil { logger.Fatalf("could not parse oidc.server parameter value %s: %s", params.OidcServer, err) } else { discoveryUrl.Path = "/.well-known/openid-configuration" } var body []byte var req *http.Request req, err = http.NewRequest(http.MethodGet, discoveryUrl.String(), bytes.NewBuffer(body)) if err != nil { return nil, err } req.Header = map[string][]string{ "Accept": {"application/json"}, } resp, err := params.APIClient.Do(req) if err != nil { return nil, err } dec := json.NewDecoder(resp.Body) discoveryResponse := &models.OpenIDConfiguration{} err = dec.Decode(discoveryResponse) if err != nil { return nil, err } ctx = context.WithValue(ctx, ctxOidcConfig, discoveryResponse) oauth2Config := &oauth2.Config{ ClientID: params.OidcClientId, ClientSecret: params.OidcClientSecret, Endpoint: oauth2.Endpoint{ AuthURL: discoveryResponse.AuthorizationEndpoint, TokenURL: discoveryResponse.TokenEndpoint, }, Scopes: []string{"openid", "offline"}, } ctx = context.WithValue(ctx, ctxOAuth2Config, oauth2Config) keySet, err := jwk.FetchHTTP(discoveryResponse.JwksUri, jwk.WithHTTPClient(params.APIClient)) if err != nil { log.Fatalf("could not fetch JWKs: %s", err) } ctx = context.WithValue(ctx, ctxOidcJwks, keySet) return ctx, nil } // Get the OpenID configuration from the context. // // DiscoverOIDC needs to be called before this is available. func GetOidcConfig(ctx context.Context) *models.OpenIDConfiguration { return ctx.Value(ctxOidcConfig).(*models.OpenIDConfiguration) } // Get the OAuth 2 configuration configuration from the context. // // DiscoverOIDC needs to be called before this is available. func GetOAuth2Config(ctx context.Context) *oauth2.Config { return ctx.Value(ctxOAuth2Config).(*oauth2.Config) } // Get the JSON Web Key set from the context. // // DiscoverOIDC needs to be called before this is available. func GetJwkSet(ctx context.Context) *jwk.Set { return ctx.Value(ctxOidcJwks).(*jwk.Set) }