package command import ( "context" "crypto/tls" "fmt" "net/http" "time" "github.com/coreos/go-oidc/v3/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" "github.com/owncloud/ocis/v2/ocis-pkg/log" pkgmiddleware "github.com/owncloud/ocis/v2/ocis-pkg/middleware" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/store" "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" "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{ 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 { 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 { return err } err = grpc.Configure(grpc.GetClientOptions(cfg.GRPCClientTLS)...) if err != nil { return err } 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), ) if err != nil { return fmt.Errorf("Failed to initialize reverse proxy: %w", err) } { middlewares := loadMiddlewares(ctx, logger, cfg, cache) server, err := proxyHTTP.Server( proxyHTTP.Handler(handlePredefinedRoutes(cfg, logger, rp, cache, middlewares)), 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() }, } } 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) */ w.Header().Set("Location", "https://todo") 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 { 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", grpc.DefaultClient()) 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.Cache(cache), middleware.DefaultAccessTokenTTL(cfg.OIDC.UserinfoCache.TTL), middleware.HTTPClient(oidcHTTPClient), middleware.OIDCIss(cfg.OIDC.Issuer), middleware.JWKSOptions(cfg.OIDC.JWKS), middleware.AccessTokenVerifyMethod(cfg.OIDC.AccessTokenVerifyMethod), middleware.OIDCProviderFunc(func() (middleware.OIDCProvider, error) { // Initialize a provider by specifying the issuer URL. // it will fetch the keys from the issuer using the .well-known // endpoint return oidc.NewProvider( context.WithValue(ctx, oauth2.HTTPClient, oidcHTTPClient), cfg.OIDC.Issuer, ) }), )) 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 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), ), ) }