You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
3.4 KiB
Go
129 lines
3.4 KiB
Go
/*
|
|
Copyright CAcert Inc.
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
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
|
|
|
|
https://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"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/lestrrat-go/jwx/jwk"
|
|
"golang.org/x/oauth2"
|
|
|
|
"code.cacert.org/cacert/oidc-demo-app/internal/models"
|
|
)
|
|
|
|
// OidcParams defines the parameters for DiscoverOIDC
|
|
type OidcParams struct {
|
|
OidcServer string
|
|
OidcClientID string
|
|
OidcClientSecret string
|
|
APIClient *http.Client
|
|
}
|
|
|
|
type OIDCInformation struct {
|
|
KeySet jwk.Set
|
|
OAuth2Config *oauth2.Config
|
|
OIDCConfiguration *models.OpenIDConfiguration
|
|
}
|
|
|
|
// DiscoverOIDC gets 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(logger *slog.Logger, params *OidcParams) (*OIDCInformation, error) {
|
|
discoveryURL, err := url.Parse(params.OidcServer)
|
|
if err != nil {
|
|
logger.Error(
|
|
"could not parse parameter oidc.server as URL",
|
|
"oidc.server", params.OidcServer,
|
|
)
|
|
|
|
return nil, fmt.Errorf("could not parse parameter value: %w", err)
|
|
}
|
|
|
|
discoveryURL.Path = "/.well-known/openid-configuration"
|
|
|
|
var (
|
|
body []byte
|
|
req *http.Request
|
|
)
|
|
|
|
req, err = http.NewRequest(http.MethodGet, discoveryURL.String(), bytes.NewBuffer(body))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not create OIDC discovery request: %w", err)
|
|
}
|
|
|
|
req.Header = map[string][]string{
|
|
"Accept": {"application/json"},
|
|
}
|
|
|
|
resp, err := params.APIClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("call to OIDC discovery endpoint failed: %w", err)
|
|
}
|
|
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
dec := json.NewDecoder(resp.Body)
|
|
discoveryResponse := &models.OpenIDConfiguration{}
|
|
|
|
err = dec.Decode(discoveryResponse)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not decode OIDC discovery response: %w", err)
|
|
}
|
|
|
|
oauth2Config := &oauth2.Config{
|
|
ClientID: params.OidcClientID,
|
|
ClientSecret: params.OidcClientSecret,
|
|
Endpoint: oauth2.Endpoint{
|
|
AuthURL: discoveryResponse.AuthorizationEndpoint,
|
|
TokenURL: discoveryResponse.TokenEndpoint,
|
|
},
|
|
Scopes: []string{"openid", "email", "profile"},
|
|
}
|
|
|
|
const jwkFetchTimeout = 10 * time.Second
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), jwkFetchTimeout)
|
|
|
|
defer cancel()
|
|
|
|
keySet, err := jwk.Fetch(ctx, discoveryResponse.JwksURI, jwk.WithHTTPClient(params.APIClient))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not fetch JWKs: %w", err)
|
|
}
|
|
|
|
return &OIDCInformation{
|
|
KeySet: keySet,
|
|
OAuth2Config: oauth2Config,
|
|
OIDCConfiguration: discoveryResponse,
|
|
}, nil
|
|
}
|