mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-06 04:09:40 -06:00
Merge pull request #8541 from dragonchaser/static-well-knowns
[full-ci] Move static `.well-knowns` for oidc from middleware to static route
This commit is contained in:
10
changelog/unreleased/remove-well-known-oidc-middleware.md
Normal file
10
changelog/unreleased/remove-well-known-oidc-middleware.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Enhancement: Introduce staticroutes package & remove well-known OIDC middleware
|
||||
|
||||
We have introduced a new static routes package to the proxy. This package
|
||||
is responsible for serving static files and oidc well-known endpoint `/.well-known/openid-configuration`.
|
||||
We have removed the well-known middleware for OIDC and moved it
|
||||
to the newly introduced static routes module in the proxy.
|
||||
|
||||
https://github.com/owncloud/ocis/issues/6095
|
||||
https://github.com/owncloud/ocis/pull/8541
|
||||
|
||||
@@ -3,15 +3,13 @@ package command
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/owncloud/ocis/v2/services/proxy/pkg/staticroutes"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"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/urfave/cli/v2"
|
||||
@@ -135,13 +133,13 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
proxy.Config(cfg),
|
||||
)
|
||||
|
||||
lh := StaticRouteHandler{
|
||||
prefix: cfg.HTTP.Root,
|
||||
userInfoCache: userInfoCache,
|
||||
logger: logger,
|
||||
config: *cfg,
|
||||
oidcClient: oidcClient,
|
||||
proxy: rp,
|
||||
lh := staticroutes.StaticRouteHandler{
|
||||
Prefix: cfg.HTTP.Root,
|
||||
UserInfoCache: userInfoCache,
|
||||
Logger: logger,
|
||||
Config: *cfg,
|
||||
OidcClient: oidcClient,
|
||||
Proxy: rp,
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize reverse proxy: %w", err)
|
||||
@@ -150,7 +148,7 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
{
|
||||
middlewares := loadMiddlewares(ctx, logger, cfg, userInfoCache, signingKeyStore, traceProvider, *m)
|
||||
server, err := proxyHTTP.Server(
|
||||
proxyHTTP.Handler(lh.handler()),
|
||||
proxyHTTP.Handler(lh.Handler()),
|
||||
proxyHTTP.Logger(logger),
|
||||
proxyHTTP.Context(ctx),
|
||||
proxyHTTP.Config(cfg),
|
||||
@@ -201,94 +199,6 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
}
|
||||
}
|
||||
|
||||
// StaticRouteHandler defines a Route Handler for static routes
|
||||
type StaticRouteHandler struct {
|
||||
prefix string
|
||||
proxy http.Handler
|
||||
userInfoCache microstore.Store
|
||||
logger log.Logger
|
||||
config config.Config
|
||||
oidcClient oidc.OIDCClient
|
||||
}
|
||||
|
||||
func (h *StaticRouteHandler) handler() http.Handler {
|
||||
m := chi.NewMux()
|
||||
m.Route(h.prefix, func(r chi.Router) {
|
||||
// Wrapper for backchannel logout
|
||||
r.Post("/backchannel_logout", h.backchannelLogout)
|
||||
|
||||
// TODO: migrate oidc well knowns here in a second wrapper
|
||||
|
||||
// Send all requests to the proxy handler
|
||||
r.HandleFunc("/*", h.proxy.ServeHTTP)
|
||||
})
|
||||
|
||||
// Also send requests for methods unknown to chi to the proxy handler as well
|
||||
m.MethodNotAllowed(h.proxy.ServeHTTP)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
type jse struct {
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
}
|
||||
|
||||
// handle backchannel logout requests as per https://openid.net/specs/openid-connect-backchannel-1_0.html#BCRequest
|
||||
func (h *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Request) {
|
||||
// parse the application/x-www-form-urlencoded POST request
|
||||
logger := h.logger.SubloggerWithRequestID(r.Context())
|
||||
if err := r.ParseForm(); err != nil {
|
||||
logger.Warn().Err(err).Msg("ParseForm failed")
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
logoutToken, err := h.oidcClient.VerifyLogoutToken(r.Context(), r.PostFormValue("logout_token"))
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("VerifyLogoutToken failed")
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
records, err := h.userInfoCache.Read(logoutToken.SessionId)
|
||||
if errors.Is(err, microstore.ErrNotFound) || len(records) == 0 {
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Error reading userinfo cache")
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
err = h.userInfoCache.Delete(string(record.Value))
|
||||
if err != nil && !errors.Is(err, microstore.ErrNotFound) {
|
||||
// Spec requires us to return a 400 BadRequest when the session could not be destroyed
|
||||
logger.Err(err).Msg("could not delete user info from cache")
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
return
|
||||
}
|
||||
logger.Debug().Msg("Deleted userinfo from cache")
|
||||
}
|
||||
|
||||
// we can ignore errors when cleaning up the lookup table
|
||||
err = h.userInfoCache.Delete(logoutToken.SessionId)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("Failed to cleanup sessionid lookup entry")
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, nil)
|
||||
}
|
||||
|
||||
func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, userInfoCache, signingKeyStore microstore.Store, traceProvider trace.TracerProvider, metrics metrics.Metrics) alice.Chain {
|
||||
rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", cfg.GrpcClient)
|
||||
policiesProviderClient := policiessvc.NewPoliciesProviderService("com.owncloud.api.policies", cfg.GrpcClient)
|
||||
@@ -403,11 +313,6 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config,
|
||||
chimiddleware.RequestID,
|
||||
middleware.AccessLog(logger),
|
||||
middleware.HTTPSRedirect,
|
||||
middleware.OIDCWellKnownRewrite(
|
||||
logger, cfg.OIDC.Issuer,
|
||||
cfg.OIDC.RewriteWellKnown,
|
||||
oidcHTTPClient,
|
||||
),
|
||||
router.Middleware(cfg.PolicySelector, cfg.Policies, logger),
|
||||
middleware.Authentication(
|
||||
authenticators,
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
)
|
||||
|
||||
var (
|
||||
wellKnownPath = "/.well-known/openid-configuration"
|
||||
)
|
||||
|
||||
// OIDCWellKnownRewrite is a middleware that rewrites the /.well-known/openid-configuration endpoint for external IDPs.
|
||||
func OIDCWellKnownRewrite(logger log.Logger, oidcISS string, rewrite bool, oidcClient *http.Client) func(http.Handler) http.Handler {
|
||||
|
||||
oidcURL, _ := url.Parse(oidcISS)
|
||||
oidcURL.Path = path.Join(oidcURL.Path, wellKnownPath)
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if rewrite && path.Clean(r.URL.Path) == wellKnownPath {
|
||||
|
||||
wellKnownRes, err := oidcClient.Get(oidcURL.String())
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("middleware", "oidc wellknown rewrite").
|
||||
Str("url", oidcURL.String()).
|
||||
Msg("get information from url failed")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer wellKnownRes.Body.Close()
|
||||
|
||||
copyHeader(w.Header(), wellKnownRes.Header)
|
||||
w.WriteHeader(wellKnownRes.StatusCode)
|
||||
io.Copy(w, wellKnownRes.Body)
|
||||
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func copyHeader(dst, src http.Header) {
|
||||
for k, vv := range src {
|
||||
for _, v := range vv {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
64
services/proxy/pkg/staticroutes/backchannellogout.go
Normal file
64
services/proxy/pkg/staticroutes/backchannellogout.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package staticroutes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
"github.com/pkg/errors"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
)
|
||||
|
||||
// handle backchannel logout requests as per https://openid.net/specs/openid-connect-backchannel-1_0.html#BCRequest
|
||||
func (s *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Request) {
|
||||
// parse the application/x-www-form-urlencoded POST request
|
||||
logger := s.Logger.SubloggerWithRequestID(r.Context())
|
||||
if err := r.ParseForm(); err != nil {
|
||||
logger.Warn().Err(err).Msg("ParseForm failed")
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
logoutToken, err := s.OidcClient.VerifyLogoutToken(r.Context(), r.PostFormValue("logout_token"))
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("VerifyLogoutToken failed")
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
records, err := s.UserInfoCache.Read(logoutToken.SessionId)
|
||||
if errors.Is(err, microstore.ErrNotFound) || len(records) == 0 {
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Error reading userinfo cache")
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
err = s.UserInfoCache.Delete(string(record.Value))
|
||||
if err != nil && !errors.Is(err, microstore.ErrNotFound) {
|
||||
// Spec requires us to return a 400 BadRequest when the session could not be destroyed
|
||||
logger.Err(err).Msg("could not delete user info from cache")
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, jse{Error: "invalid_request", ErrorDescription: err.Error()})
|
||||
return
|
||||
}
|
||||
logger.Debug().Msg("Deleted userinfo from cache")
|
||||
}
|
||||
|
||||
// we can ignore errors when cleaning up the lookup table
|
||||
err = s.UserInfoCache.Delete(logoutToken.SessionId)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("Failed to cleanup sessionid lookup entry")
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, nil)
|
||||
}
|
||||
45
services/proxy/pkg/staticroutes/oidc_well-known.go
Normal file
45
services/proxy/pkg/staticroutes/oidc_well-known.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package staticroutes
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
wellKnownPath = "/.well-known/openid-configuration"
|
||||
)
|
||||
|
||||
// OIDCWellKnownRewrite is a handler that rewrites the /.well-known/openid-configuration endpoint for external IDPs.
|
||||
func (s *StaticRouteHandler) oIDCWellKnownRewrite(w http.ResponseWriter, r *http.Request) {
|
||||
wellKnownRes, err := s.OidcHttpClient.Get(s.oidcURL.String())
|
||||
if err != nil {
|
||||
s.Logger.Error().
|
||||
Err(err).
|
||||
Str("handler", "oidc wellknown rewrite").
|
||||
Str("url", s.oidcURL.String()).
|
||||
Msg("get information from url failed")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer wellKnownRes.Body.Close()
|
||||
|
||||
copyHeader(w.Header(), wellKnownRes.Header)
|
||||
w.WriteHeader(wellKnownRes.StatusCode)
|
||||
_, err = io.Copy(w, wellKnownRes.Body)
|
||||
if err != nil {
|
||||
s.Logger.Error().
|
||||
Err(err).
|
||||
Str("handler", "oidc wellknown rewrite").
|
||||
Msg("copying response body failed")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func copyHeader(dst, src http.Header) {
|
||||
for k, vv := range src {
|
||||
for _, v := range vv {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
55
services/proxy/pkg/staticroutes/staticroutes.go
Normal file
55
services/proxy/pkg/staticroutes/staticroutes.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package staticroutes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/oidc"
|
||||
"github.com/owncloud/ocis/v2/services/proxy/pkg/config"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
)
|
||||
|
||||
// StaticRouteHandler defines a Route Handler for static routes
|
||||
type StaticRouteHandler struct {
|
||||
Prefix string
|
||||
Proxy http.Handler
|
||||
UserInfoCache microstore.Store
|
||||
Logger log.Logger
|
||||
Config config.Config
|
||||
OidcClient oidc.OIDCClient
|
||||
OidcHttpClient *http.Client
|
||||
|
||||
oidcURL *url.URL
|
||||
}
|
||||
|
||||
type jse struct {
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
}
|
||||
|
||||
func (s *StaticRouteHandler) Handler() http.Handler {
|
||||
s.oidcURL, _ = url.Parse(s.Config.OIDC.Issuer)
|
||||
s.oidcURL.Path = path.Join(s.oidcURL.Path, wellKnownPath)
|
||||
m := chi.NewMux()
|
||||
m.Route(s.Prefix, func(r chi.Router) {
|
||||
|
||||
// Wrapper for backchannel logout
|
||||
r.Post("/backchannel_logout", s.backchannelLogout)
|
||||
|
||||
// openid .well-known
|
||||
if s.Config.OIDC.RewriteWellKnown {
|
||||
r.Get("/.well-known/openid-configuration", s.oIDCWellKnownRewrite)
|
||||
}
|
||||
|
||||
// Send all requests to the proxy handler
|
||||
r.HandleFunc("/*", s.Proxy.ServeHTTP)
|
||||
})
|
||||
|
||||
// Also send requests for methods unknown to chi to the proxy handler as well
|
||||
m.MethodNotAllowed(s.Proxy.ServeHTTP)
|
||||
|
||||
return m
|
||||
}
|
||||
Reference in New Issue
Block a user