mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-07 12:50:21 -06:00
This reverts commit 52951b42b0.
The change broke authentication for at least the desktop client when
using the builtin idp. There seem to be issues in the IDP (lico) which
result in the implicit scoped not being added correctly in some case.
When that scope is missing the `lg.uuid` claim will not be present in
the userinfo and we can correctly match users by id.
This reverts back to the old behaviour of matching users by name. Which
also brings some aspects of https://github.com/owncloud/ocis/issues/904
Fixes #6415
420 lines
13 KiB
Go
420 lines
13 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
|
"github.com/cs3org/reva/v2/pkg/store"
|
|
"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"
|
|
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
|
pkgmiddleware "github.com/owncloud/ocis/v2/ocis-pkg/middleware"
|
|
"github.com/owncloud/ocis/v2/ocis-pkg/oidc"
|
|
"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
|
|
"github.com/owncloud/ocis/v2/ocis-pkg/version"
|
|
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
|
storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/autoprovision"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/config"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/config/parser"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/logging"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/metrics"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/middleware"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/proxy"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/router"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/server/debug"
|
|
proxyHTTP "github.com/owncloud/ocis/v2/services/proxy/pkg/server/http"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/tracing"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend"
|
|
"github.com/owncloud/ocis/v2/services/proxy/pkg/userroles"
|
|
"github.com/urfave/cli/v2"
|
|
microstore "go-micro.dev/v4/store"
|
|
)
|
|
|
|
// Server is the entrypoint for the server command.
|
|
func Server(cfg *config.Config) *cli.Command {
|
|
return &cli.Command{
|
|
Name: "server",
|
|
Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name),
|
|
Category: "server",
|
|
Before: func(c *cli.Context) error {
|
|
return configlog.ReturnFatal(parser.ParseConfig(cfg))
|
|
},
|
|
Action: func(c *cli.Context) error {
|
|
userInfoCache := 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 {
|
|
return err
|
|
}
|
|
err = grpc.Configure(grpc.GetClientOptions(cfg.GRPCClientTLS)...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var oidcHTTPClient = &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
InsecureSkipVerify: cfg.OIDC.Insecure, //nolint:gosec
|
|
},
|
|
DisableKeepAlives: true,
|
|
},
|
|
Timeout: time.Second * 10,
|
|
}
|
|
|
|
oidcClient := oidc.NewOIDCClient(
|
|
oidc.WithAccessTokenVerifyMethod(cfg.OIDC.AccessTokenVerifyMethod),
|
|
oidc.WithLogger(logger),
|
|
oidc.WithHTTPClient(oidcHTTPClient),
|
|
oidc.WithOidcIssuer(cfg.OIDC.Issuer),
|
|
oidc.WithJWKSOptions(cfg.OIDC.JWKS),
|
|
)
|
|
|
|
var (
|
|
m = metrics.New()
|
|
)
|
|
|
|
gr := run.Group{}
|
|
ctx, cancel := func() (context.Context, context.CancelFunc) {
|
|
if cfg.Context == nil {
|
|
return context.WithCancel(context.Background())
|
|
}
|
|
return context.WithCancel(cfg.Context)
|
|
}()
|
|
|
|
defer cancel()
|
|
|
|
m.BuildInfo.WithLabelValues(version.GetString()).Set(1)
|
|
|
|
rp, err := proxy.NewMultiHostReverseProxy(
|
|
proxy.Logger(logger),
|
|
proxy.Config(cfg),
|
|
)
|
|
|
|
lh := 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)
|
|
}
|
|
|
|
{
|
|
middlewares := loadMiddlewares(ctx, logger, cfg, userInfoCache)
|
|
server, err := proxyHTTP.Server(
|
|
proxyHTTP.Handler(lh.handler()),
|
|
proxyHTTP.Logger(logger),
|
|
proxyHTTP.Context(ctx),
|
|
proxyHTTP.Config(cfg),
|
|
proxyHTTP.Metrics(metrics.New()),
|
|
proxyHTTP.Middlewares(middlewares),
|
|
)
|
|
|
|
if err != nil {
|
|
logger.Error().
|
|
Err(err).
|
|
Str("server", "http").
|
|
Msg("Failed to initialize server")
|
|
|
|
return err
|
|
}
|
|
|
|
gr.Add(func() error {
|
|
return server.Run()
|
|
}, func(err error) {
|
|
logger.Error().
|
|
Err(err).
|
|
Str("server", "http").
|
|
Msg("Shutting down server")
|
|
|
|
cancel()
|
|
})
|
|
}
|
|
|
|
{
|
|
server, err := debug.Server(
|
|
debug.Logger(logger),
|
|
debug.Context(ctx),
|
|
debug.Config(cfg),
|
|
)
|
|
|
|
if err != nil {
|
|
logger.Error().Err(err).Str("server", "debug").Msg("Failed to initialize server")
|
|
return err
|
|
}
|
|
|
|
gr.Add(server.ListenAndServe, func(_ error) {
|
|
_ = server.Shutdown(ctx)
|
|
cancel()
|
|
})
|
|
}
|
|
|
|
return gr.Run()
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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
|
|
r.HandleFunc("/*", h.proxy.ServeHTTP)
|
|
|
|
})
|
|
// This is commented out due to a race issue in chi
|
|
//var methods = []string{"PROPFIND", "DELETE", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", "REPORT"}
|
|
//for _, k := range methods {
|
|
// chi.RegisterMethod(k)
|
|
//}
|
|
|
|
// To avoid using the chi.RegisterMethod() this is basically a catchAll for all HTTP Methods that are not
|
|
// covered in chi by default
|
|
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 microstore.Store) alice.Chain {
|
|
grpcClient, err := grpc.NewClient(append(grpc.GetClientOptions(cfg.GRPCClientTLS), grpc.WithTraceProvider(tracing.TraceProvider))...)
|
|
if err != nil {
|
|
logger.Fatal().Err(err).Msg("Failed to get gateway client")
|
|
}
|
|
rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", grpcClient)
|
|
revaClient, err := pool.GetGatewayServiceClient(cfg.Reva.Address, cfg.Reva.GetRevaOptions()...)
|
|
if err != nil {
|
|
logger.Fatal().Err(err).Msg("Failed to get gateway client")
|
|
}
|
|
tokenManager, err := jwt.New(map[string]interface{}{
|
|
"secret": cfg.TokenManager.JWTSecret,
|
|
})
|
|
if err != nil {
|
|
logger.Fatal().Err(err).
|
|
Msg("Failed to create token manager")
|
|
}
|
|
autoProvsionCreator := autoprovision.NewCreator(autoprovision.WithTokenManager(tokenManager))
|
|
var userProvider backend.UserBackend
|
|
switch cfg.AccountBackend {
|
|
case "cs3":
|
|
|
|
userProvider = backend.NewCS3UserBackend(
|
|
backend.WithLogger(logger),
|
|
backend.WithRevaAuthenticator(revaClient),
|
|
backend.WithMachineAuthAPIKey(cfg.MachineAuthAPIKey),
|
|
backend.WithOIDCissuer(cfg.OIDC.Issuer),
|
|
backend.WithAutoProvisonCreator(autoProvsionCreator),
|
|
)
|
|
default:
|
|
logger.Fatal().Msgf("Invalid accounts backend type '%s'", cfg.AccountBackend)
|
|
}
|
|
|
|
var roleAssigner userroles.UserRoleAssigner
|
|
switch cfg.RoleAssignment.Driver {
|
|
case "default":
|
|
roleAssigner = userroles.NewDefaultRoleAssigner(
|
|
userroles.WithRoleService(rolesClient),
|
|
userroles.WithLogger(logger),
|
|
)
|
|
case "oidc":
|
|
roleAssigner = userroles.NewOIDCRoleAssigner(
|
|
userroles.WithRoleService(rolesClient),
|
|
userroles.WithLogger(logger),
|
|
userroles.WithRolesClaim(cfg.RoleAssignment.OIDCRoleMapper.RoleClaim),
|
|
userroles.WithRoleMapping(cfg.RoleAssignment.OIDCRoleMapper.RolesMap),
|
|
userroles.WithAutoProvisonCreator(autoProvsionCreator),
|
|
)
|
|
default:
|
|
logger.Fatal().Msgf("Invalid role assignment driver '%s'", cfg.RoleAssignment.Driver)
|
|
}
|
|
|
|
storeClient := storesvc.NewStoreService("com.owncloud.api.store", grpcClient)
|
|
if err != nil {
|
|
logger.Error().Err(err).
|
|
Str("gateway", cfg.Reva.Address).
|
|
Msg("Failed to create reva gateway service client")
|
|
}
|
|
|
|
var oidcHTTPClient = &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
InsecureSkipVerify: cfg.OIDC.Insecure, //nolint:gosec
|
|
},
|
|
DisableKeepAlives: true,
|
|
},
|
|
Timeout: time.Second * 10,
|
|
}
|
|
|
|
var authenticators []middleware.Authenticator
|
|
if cfg.EnableBasicAuth {
|
|
logger.Warn().Msg("basic auth enabled, use only for testing or development")
|
|
authenticators = append(authenticators, middleware.BasicAuthenticator{
|
|
Logger: logger,
|
|
UserProvider: userProvider,
|
|
})
|
|
}
|
|
|
|
authenticators = append(authenticators, middleware.NewOIDCAuthenticator(
|
|
middleware.Logger(logger),
|
|
middleware.UserInfoCache(userInfoCache),
|
|
middleware.DefaultAccessTokenTTL(cfg.OIDC.UserinfoCache.TTL),
|
|
middleware.HTTPClient(oidcHTTPClient),
|
|
middleware.OIDCIss(cfg.OIDC.Issuer),
|
|
middleware.OIDCClient(oidc.NewOIDCClient(
|
|
oidc.WithAccessTokenVerifyMethod(cfg.OIDC.AccessTokenVerifyMethod),
|
|
oidc.WithLogger(logger),
|
|
oidc.WithHTTPClient(oidcHTTPClient),
|
|
oidc.WithOidcIssuer(cfg.OIDC.Issuer),
|
|
oidc.WithJWKSOptions(cfg.OIDC.JWKS),
|
|
)),
|
|
))
|
|
authenticators = append(authenticators, middleware.PublicShareAuthenticator{
|
|
Logger: logger,
|
|
RevaGatewayClient: revaClient,
|
|
})
|
|
authenticators = append(authenticators, middleware.SignedURLAuthenticator{
|
|
Logger: logger,
|
|
PreSignedURLConfig: cfg.PreSignedURL,
|
|
UserProvider: userProvider,
|
|
UserRoleAssigner: roleAssigner,
|
|
Store: storeClient,
|
|
})
|
|
|
|
return alice.New(
|
|
// first make sure we log all requests and redirect to https if necessary
|
|
middleware.Tracer(),
|
|
pkgmiddleware.TraceContext,
|
|
chimiddleware.RealIP,
|
|
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,
|
|
middleware.CredentialsByUserAgent(cfg.AuthMiddleware.CredentialsByUserAgent),
|
|
middleware.Logger(logger),
|
|
middleware.OIDCIss(cfg.OIDC.Issuer),
|
|
middleware.EnableBasicAuth(cfg.EnableBasicAuth),
|
|
),
|
|
middleware.AccountResolver(
|
|
middleware.Logger(logger),
|
|
middleware.UserProvider(userProvider),
|
|
middleware.UserRoleAssigner(roleAssigner),
|
|
middleware.UserOIDCClaim(cfg.UserOIDCClaim),
|
|
middleware.UserCS3Claim(cfg.UserCS3Claim),
|
|
middleware.AutoprovisionAccounts(cfg.AutoprovisionAccounts),
|
|
),
|
|
middleware.SelectorCookie(
|
|
middleware.Logger(logger),
|
|
middleware.PolicySelectorConfig(*cfg.PolicySelector),
|
|
),
|
|
middleware.Policies(logger, cfg.PoliciesMiddleware.Query),
|
|
// finally, trigger home creation when a user logs in
|
|
middleware.CreateHome(
|
|
middleware.Logger(logger),
|
|
middleware.RevaGatewayClient(revaClient),
|
|
middleware.RoleQuotas(cfg.RoleQuotas),
|
|
),
|
|
)
|
|
}
|