Files
wonderwall/pkg/openid/client/client.go
Trong Huu Nguyen 66cf08e602 refactor(openid/logout): simplify logout logic
As we already clear any local sessions before redirecting to the
Identity Provider, and the callback always redirects to a pre-configured URL,
there isn't really any need to maintain and verify state in the logout
callback.

In other words, the logout callback handler is simply a redirect handler.
2022-07-12 15:09:49 +02:00

140 lines
3.6 KiB
Go

package client
import (
"context"
"fmt"
"net/http"
"time"
"github.com/google/uuid"
"github.com/lestrrat-go/jwx/v2/jwt"
"golang.org/x/oauth2"
"github.com/nais/wonderwall/pkg/openid"
openidconfig "github.com/nais/wonderwall/pkg/openid/config"
"github.com/nais/wonderwall/pkg/openid/provider"
)
type Client interface {
config() openidconfig.Config
oAuth2Config() *oauth2.Config
Login(r *http.Request) (Login, error)
LoginCallback(r *http.Request, p provider.Provider, cookie *openid.LoginCookie) (LoginCallback, error)
Logout() (Logout, error)
LogoutCallback(r *http.Request) LogoutCallback
LogoutFrontchannel(r *http.Request) LogoutFrontchannel
AuthCodeGrant(ctx context.Context, code string, opts []oauth2.AuthCodeOption) (*oauth2.Token, error)
MakeAssertion(expiration time.Duration) (string, error)
RefreshGrant(r *http.Request) error
}
type client struct {
cfg openidconfig.Config
oauth2Config *oauth2.Config
}
func NewClient(cfg openidconfig.Config) Client {
oauth2Config := &oauth2.Config{
ClientID: cfg.Client().GetClientID(),
Endpoint: oauth2.Endpoint{
AuthURL: cfg.Provider().AuthorizationEndpoint,
TokenURL: cfg.Provider().TokenEndpoint,
AuthStyle: oauth2.AuthStyleInParams,
},
RedirectURL: cfg.Client().GetCallbackURI(),
Scopes: cfg.Client().GetScopes(),
}
return &client{
cfg: cfg,
oauth2Config: oauth2Config,
}
}
func (c client) config() openidconfig.Config {
return c.cfg
}
func (c client) oAuth2Config() *oauth2.Config {
return c.oauth2Config
}
func (c client) Login(r *http.Request) (Login, error) {
login, err := NewLogin(c, r)
if err != nil {
return nil, fmt.Errorf("login: %w", err)
}
return login, nil
}
func (c client) LoginCallback(r *http.Request, p provider.Provider, cookie *openid.LoginCookie) (LoginCallback, error) {
loginCallback, err := NewLoginCallback(c, r, p, cookie)
if err != nil {
return nil, fmt.Errorf("callback: %w", err)
}
return loginCallback, nil
}
func (c client) Logout() (Logout, error) {
logout, err := NewLogout(c)
if err != nil {
return nil, fmt.Errorf("logout: %w", err)
}
return logout, nil
}
func (c client) LogoutCallback(r *http.Request) LogoutCallback {
return NewLogoutCallback(c, r)
}
func (c client) LogoutFrontchannel(r *http.Request) LogoutFrontchannel {
return NewLogoutFrontchannel(r)
}
func (c client) AuthCodeGrant(ctx context.Context, code string, opts []oauth2.AuthCodeOption) (*oauth2.Token, error) {
return c.oauth2Config.Exchange(ctx, code, opts...)
}
func (c client) MakeAssertion(expiration time.Duration) (string, error) {
clientCfg := c.config().Client()
providerCfg := c.config().Provider()
key := clientCfg.GetClientJWK()
iat := time.Now()
exp := iat.Add(expiration)
errs := make([]error, 0)
tok := jwt.New()
errs = append(errs, tok.Set(jwt.IssuerKey, clientCfg.GetClientID()))
errs = append(errs, tok.Set(jwt.SubjectKey, clientCfg.GetClientID()))
errs = append(errs, tok.Set(jwt.AudienceKey, providerCfg.Issuer))
errs = append(errs, tok.Set("scope", clientCfg.GetScopes().String()))
errs = append(errs, tok.Set(jwt.IssuedAtKey, iat))
errs = append(errs, tok.Set(jwt.ExpirationKey, exp))
errs = append(errs, tok.Set(jwt.JwtIDKey, uuid.New().String()))
for _, err := range errs {
if err != nil {
return "", fmt.Errorf("setting claim for client assertion: %w", err)
}
}
encoded, err := jwt.Sign(tok, jwt.WithKey(key.Algorithm(), key))
if err != nil {
return "", fmt.Errorf("signing client assertion: %w", err)
}
return string(encoded), nil
}
func (c client) RefreshGrant(r *http.Request) error {
//TODO implement me
panic("implement me")
}