extract full claims from jwt token to get session id
Signed-off-by: Christian Richter <crichter@owncloud.com>
This commit is contained in:
@@ -8,9 +8,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
ocisLogoutVerifier "github.com/owncloud/ocis/v2/ocis-pkg/oidc"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/cs3org/reva/v2/pkg/token/manager/jwt"
|
||||
"github.com/go-chi/chi/v5"
|
||||
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/justinas/alice"
|
||||
"github.com/oklog/run"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
|
||||
@@ -39,6 +43,22 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type LogoutHandler struct {
|
||||
cache microstore.Store
|
||||
logger log.Logger
|
||||
config config.Config
|
||||
}
|
||||
|
||||
type LogoutToken struct {
|
||||
iss string `json:iss` // example "https://server.example.com"
|
||||
sub int64 `json:sub` //"248289761001"
|
||||
aud string `json:aud` // "s6BhdRkqt3"
|
||||
iat int64 `json:iat` // 1471566154
|
||||
jti string `json:jti` // "bWJq"
|
||||
sid string `json:sid` // "08a5019c-17e1-4977-8f42-65a12843ea02"
|
||||
events map[string][]string `json:events` // {"http://schemas.openid.net/event/backchannel-logout": {}}
|
||||
}
|
||||
|
||||
// Server is the entrypoint for the server command.
|
||||
func Server(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
@@ -49,6 +69,15 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
return configlog.ReturnFatal(parser.ParseConfig(cfg))
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
cache := store.Create(
|
||||
store.Store(cfg.OIDC.UserinfoCache.Store),
|
||||
store.TTL(cfg.OIDC.UserinfoCache.TTL),
|
||||
store.Size(cfg.OIDC.UserinfoCache.Size),
|
||||
microstore.Nodes(cfg.OIDC.UserinfoCache.Nodes...),
|
||||
microstore.Database(cfg.OIDC.UserinfoCache.Database),
|
||||
microstore.Table(cfg.OIDC.UserinfoCache.Table),
|
||||
)
|
||||
|
||||
logger := logging.Configure(cfg.Service.Name, cfg.Log)
|
||||
err := tracing.Configure(cfg)
|
||||
if err != nil {
|
||||
@@ -84,13 +113,14 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
}
|
||||
|
||||
{
|
||||
middlewares := loadMiddlewares(ctx, logger, cfg, cache)
|
||||
server, err := proxyHTTP.Server(
|
||||
proxyHTTP.Handler(rp),
|
||||
proxyHTTP.Handler(handlePredefinedRoutes(cfg, logger, rp, cache, middlewares)),
|
||||
proxyHTTP.Logger(logger),
|
||||
proxyHTTP.Context(ctx),
|
||||
proxyHTTP.Config(cfg),
|
||||
proxyHTTP.Metrics(metrics.New()),
|
||||
proxyHTTP.Middlewares(loadMiddlewares(ctx, logger, cfg)),
|
||||
proxyHTTP.Middlewares(middlewares),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -137,7 +167,50 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config) alice.Chain {
|
||||
func handlePredefinedRoutes(cfg *config.Config, logger log.Logger, handler http.Handler, cache microstore.Store, middlewares alice.Chain) http.Handler {
|
||||
m := chi.NewMux()
|
||||
var methods = []string{"PROPFIND", "DELETE", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK"}
|
||||
for _, k := range methods {
|
||||
chi.RegisterMethod(k)
|
||||
}
|
||||
|
||||
p := LogoutHandler{
|
||||
cache: cache,
|
||||
logger: logger,
|
||||
config: *cfg,
|
||||
}
|
||||
|
||||
m.Route(cfg.HTTP.Root, func(r chi.Router) {
|
||||
// Wrapper for backchannel logout
|
||||
// TODO: remove the GET for the backchannel_logout
|
||||
r.Get("/backchannel_logout", p.backchannelLogout)
|
||||
r.Post("/backchannel_logout", p.backchannelLogout)
|
||||
// TODO: migrate oidc well knowns here in a second wrapper
|
||||
r.HandleFunc("/*", handler.ServeHTTP)
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
func (p *LogoutHandler) backchannelLogout(w http.ResponseWriter, r *http.Request) {
|
||||
var oidcHTTPClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: p.config.OIDC.Insecure, //nolint:gosec
|
||||
},
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
Timeout: time.Second * 10,
|
||||
}
|
||||
prov, _ := oidc.NewProvider(
|
||||
context.WithValue(context.Background(), oauth2.HTTPClient, oidcHTTPClient),
|
||||
p.config.OIDC.Issuer,
|
||||
)
|
||||
logoutVerifier := ocisLogoutVerifier.NewLogoutVerifier(p.config.OIDC)
|
||||
render.Status(r, http.StatusOK)
|
||||
}
|
||||
|
||||
func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, cache microstore.Store) alice.Chain {
|
||||
rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient())
|
||||
revaClient, err := pool.GetGatewayServiceClient(cfg.Reva.Address, cfg.Reva.GetRevaOptions()...)
|
||||
if err != nil {
|
||||
@@ -212,15 +285,6 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config)
|
||||
})
|
||||
}
|
||||
|
||||
cache := store.Create(
|
||||
store.Store(cfg.OIDC.UserinfoCache.Store),
|
||||
store.TTL(cfg.OIDC.UserinfoCache.TTL),
|
||||
store.Size(cfg.OIDC.UserinfoCache.Size),
|
||||
microstore.Nodes(cfg.OIDC.UserinfoCache.Nodes...),
|
||||
microstore.Database(cfg.OIDC.UserinfoCache.Database),
|
||||
microstore.Table(cfg.OIDC.UserinfoCache.Table),
|
||||
)
|
||||
|
||||
authenticators = append(authenticators, middleware.NewOIDCAuthenticator(
|
||||
middleware.Logger(logger),
|
||||
middleware.Cache(cache),
|
||||
|
||||
@@ -3,6 +3,7 @@ package middleware
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -39,6 +40,7 @@ func NewOIDCAuthenticator(opts ...Option) *OIDCAuthenticator {
|
||||
return &OIDCAuthenticator{
|
||||
Logger: options.Logger,
|
||||
userInfoCache: options.Cache,
|
||||
sessionLookupCache: options.Cache,
|
||||
DefaultTokenCacheTTL: options.DefaultAccessTokenTTL,
|
||||
HTTPClient: options.HTTPClient,
|
||||
OIDCIss: options.OIDCIss,
|
||||
@@ -56,6 +58,7 @@ type OIDCAuthenticator struct {
|
||||
HTTPClient *http.Client
|
||||
OIDCIss string
|
||||
userInfoCache store.Store
|
||||
sessionLookupCache store.Store
|
||||
DefaultTokenCacheTTL time.Duration
|
||||
ProviderFunc func() (OIDCProvider, error)
|
||||
AccessTokenVerifyMethod string
|
||||
@@ -87,7 +90,16 @@ func (m *OIDCAuthenticator) getClaims(token string, req *http.Request) (map[stri
|
||||
}
|
||||
m.Logger.Error().Err(err).Msg("could not unmarshal userinfo")
|
||||
}
|
||||
aClaims, err := m.verifyAccessToken(token)
|
||||
// TODO: use mClaims
|
||||
aClaims, mClaims, err := m.verifyAccessToken(token)
|
||||
//fmt.Println(mClaims)
|
||||
vals := make([]string, len(mClaims))
|
||||
for k, v := range mClaims {
|
||||
s, _ := base64.StdEncoding.DecodeString(v)
|
||||
vals[k] = string(s)
|
||||
}
|
||||
fmt.Println(vals)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to verify access token")
|
||||
}
|
||||
@@ -120,6 +132,17 @@ func (m *OIDCAuthenticator) getClaims(token string, req *http.Request) (map[stri
|
||||
if err != nil {
|
||||
m.Logger.Error().Err(err).Msg("failed to write to userinfo cache")
|
||||
}
|
||||
|
||||
if sid, ok := claims["sid"]; ok {
|
||||
err = m.sessionLookupCache.Write(&store.Record{
|
||||
Key: fmt.Sprintf("%s", sid),
|
||||
Value: []byte(encodedHash),
|
||||
Expiry: time.Until(expiration),
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
m.Logger.Error().Err(err).Msg("failed to write session lookup cache")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -127,42 +150,52 @@ func (m *OIDCAuthenticator) getClaims(token string, req *http.Request) (map[stri
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (m OIDCAuthenticator) verifyAccessToken(token string) (jwt.RegisteredClaims, error) {
|
||||
// TODO: update jwt lib to have access to session id, or extract the session id and return it
|
||||
func (m OIDCAuthenticator) verifyAccessToken(token string) (jwt.RegisteredClaims, []string, error) {
|
||||
var mapClaims []string
|
||||
switch m.AccessTokenVerifyMethod {
|
||||
case config.AccessTokenVerificationJWT:
|
||||
return m.verifyAccessTokenJWT(token)
|
||||
case config.AccessTokenVerificationNone:
|
||||
m.Logger.Debug().Msg("Access Token verification disabled")
|
||||
return jwt.RegisteredClaims{}, nil
|
||||
return jwt.RegisteredClaims{}, mapClaims, nil
|
||||
default:
|
||||
m.Logger.Error().Str("access_token_verify_method", m.AccessTokenVerifyMethod).Msg("Unknown Access Token verification setting")
|
||||
return jwt.RegisteredClaims{}, errors.New("Unknown Access Token Verification method")
|
||||
return jwt.RegisteredClaims{}, mapClaims, errors.New("Unknown Access Token Verification method")
|
||||
}
|
||||
}
|
||||
|
||||
// verifyAccessTokenJWT tries to parse and verify the access token as a JWT.
|
||||
func (m OIDCAuthenticator) verifyAccessTokenJWT(token string) (jwt.RegisteredClaims, error) {
|
||||
func (m OIDCAuthenticator) verifyAccessTokenJWT(token string) (jwt.RegisteredClaims, []string, error) {
|
||||
var claims jwt.RegisteredClaims
|
||||
var mapClaims []string
|
||||
jwks := m.getKeyfunc()
|
||||
if jwks == nil {
|
||||
return claims, errors.New("Error initializing jwks keyfunc")
|
||||
return claims, mapClaims, errors.New("Error initializing jwks keyfunc")
|
||||
}
|
||||
|
||||
_, err := jwt.ParseWithClaims(token, &claims, jwks.Keyfunc)
|
||||
_, mapClaims, err = new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
|
||||
// TODO: decode mapClaims to sth readable
|
||||
m.Logger.Debug().Interface("access token", &claims).Msg("parsed access token")
|
||||
if err != nil {
|
||||
m.Logger.Info().Err(err).Msg("Failed to parse/verify the access token.")
|
||||
return claims, err
|
||||
return claims, mapClaims, err
|
||||
}
|
||||
m.Logger.Debug().Interface("access token", &claims).Msg("parsed access token")
|
||||
if err != nil {
|
||||
m.Logger.Info().Err(err).Msg("Failed to parse/verify the access token.")
|
||||
return claims, mapClaims, err
|
||||
}
|
||||
|
||||
if !claims.VerifyIssuer(m.OIDCIss, true) {
|
||||
vErr := jwt.ValidationError{}
|
||||
vErr.Inner = jwt.ErrTokenInvalidIssuer
|
||||
vErr.Errors |= jwt.ValidationErrorIssuer
|
||||
return claims, vErr
|
||||
return claims, mapClaims, vErr
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
return claims, mapClaims, nil
|
||||
}
|
||||
|
||||
// extractExpiration tries to extract the expriration time from the access token
|
||||
|
||||
Reference in New Issue
Block a user