From 7c17ddb0b0ed75137bc76cbc5c111f7750600d6b Mon Sep 17 00:00:00 2001 From: David Christofas Date: Thu, 9 Feb 2023 16:18:44 +0100 Subject: [PATCH] add a permission check to the logo upload --- .../pkg/config/defaults/defaultconfig.go | 10 +++---- services/settings/pkg/service/v0/settings.go | 23 +++++++++++++++ .../settings/pkg/store/defaults/defaults.go | 20 +++++++++++++ services/web/pkg/config/config.go | 25 +++++++++++------ .../web/pkg/config/defaults/defaultconfig.go | 8 ++++++ services/web/pkg/config/parser/parse.go | 4 +++ services/web/pkg/server/http/server.go | 7 +++++ services/web/pkg/service/v0/branding.go | 22 +++++++++++++++ services/web/pkg/service/v0/option.go | 15 ++++++++-- services/web/pkg/service/v0/service.go | 28 +++++++++++++------ 10 files changed, 137 insertions(+), 25 deletions(-) diff --git a/services/proxy/pkg/config/defaults/defaultconfig.go b/services/proxy/pkg/config/defaults/defaultconfig.go index d9f69f600..af5a71204 100644 --- a/services/proxy/pkg/config/defaults/defaultconfig.go +++ b/services/proxy/pkg/config/defaults/defaultconfig.go @@ -1,7 +1,6 @@ package defaults import ( - "net/http" "path" "strings" @@ -84,6 +83,10 @@ func DefaultPolicies() []config.Policy { Service: "com.owncloud.web.idp", Unprotected: true, }, + { + Endpoint: "/branding/logo", + Service: "com.owncloud.web.web", + }, { Endpoint: "/konnect/", Service: "com.owncloud.web.idp", @@ -199,11 +202,6 @@ func DefaultPolicies() []config.Policy { Endpoint: "/api/v0/settings", Service: "com.owncloud.web.settings", }, - { - Method: http.MethodPost, - Endpoint: "/branding/logo", - Service: "com.owncloud.web.web", - }, }, }, } diff --git a/services/settings/pkg/service/v0/settings.go b/services/settings/pkg/service/v0/settings.go index 481ff0784..411e6310b 100644 --- a/services/settings/pkg/service/v0/settings.go +++ b/services/settings/pkg/service/v0/settings.go @@ -57,6 +57,11 @@ const ( SelfManagementPermissionID string = "e03070e9-4362-4cc6-a872-1c7cb2eb2b8e" // SelfManagementPermissionName is the hardcoded setting name for the self management permission SelfManagementPermissionName string = "self-management" + + // ChangeLogoPermissionID is the hardcoded setting UUID for the change-logo permission + ChangeLogoPermissionID string = "ed83fc10-1f54-4a9e-b5a7-fb517f5f3e01" + // ChangeLogoPermissionName is the hardcoded setting name for the change-logo permission + ChangeLogoPermissionName string = "change-logo" ) // generateBundlesDefaultRoles bootstraps the default roles. @@ -438,6 +443,24 @@ func generatePermissionRequests() []*settingssvc.AddSettingToBundleRequest { }, }, }, + { + BundleId: BundleUUIDRoleAdmin, + Setting: &settingsmsg.Setting{ + Id: ChangeLogoPermissionID, + Name: ChangeLogoPermissionName, + DisplayName: "Change logo", + Description: "This permission permits to change the system logo.", + Resource: &settingsmsg.Resource{ + Type: settingsmsg.Resource_TYPE_SYSTEM, + }, + Value: &settingsmsg.Setting_PermissionValue{ + PermissionValue: &settingsmsg.Permission{ + Operation: settingsmsg.Permission_OPERATION_READWRITE, + Constraint: settingsmsg.Permission_CONSTRAINT_ALL, + }, + }, + }, + }, { BundleId: BundleUUIDRoleSpaceAdmin, Setting: &settingsmsg.Setting{ diff --git a/services/settings/pkg/store/defaults/defaults.go b/services/settings/pkg/store/defaults/defaults.go index 52cc0b669..6486b5415 100644 --- a/services/settings/pkg/store/defaults/defaults.go +++ b/services/settings/pkg/store/defaults/defaults.go @@ -82,6 +82,11 @@ const ( SelfManagementPermissionID string = "e03070e9-4362-4cc6-a872-1c7cb2eb2b8e" // SelfManagementPermissionName is the hardcoded setting name for the self management permission SelfManagementPermissionName string = "self-management" + + // ChangeLogoPermissionID is the hardcoded setting UUID for the change-logo permission + ChangeLogoPermissionID string = "ed83fc10-1f54-4a9e-b5a7-fb517f5f3e01" + // ChangeLogoPermissionName is the hardcoded setting name for the change-logo permission + ChangeLogoPermissionName string = "change-logo" ) // GenerateBundlesDefaultRoles bootstraps the default roles. @@ -260,6 +265,21 @@ func generateBundleAdminRole() *settingsmsg.Bundle { }, }, }, + { + Id: ChangeLogoPermissionID, + Name: ChangeLogoPermissionName, + DisplayName: "Change logo", + Description: "This permission permits to change the system logo.", + Resource: &settingsmsg.Resource{ + Type: settingsmsg.Resource_TYPE_SYSTEM, + }, + Value: &settingsmsg.Setting_PermissionValue{ + PermissionValue: &settingsmsg.Permission{ + Operation: settingsmsg.Permission_OPERATION_READWRITE, + Constraint: settingsmsg.Permission_CONSTRAINT_ALL, + }, + }, + }, }, } } diff --git a/services/web/pkg/config/config.go b/services/web/pkg/config/config.go index 6259d7666..bd61b69d1 100644 --- a/services/web/pkg/config/config.go +++ b/services/web/pkg/config/config.go @@ -22,7 +22,10 @@ type Config struct { File string `yaml:"file" env:"WEB_UI_CONFIG" desc:"Read the ownCloud Web configuration from this file."` // TODO: rename this to a more self explaining string Web Web `yaml:"web"` - Context context.Context `yaml:"-"` + TokenManager *TokenManager `yaml:"token_manager"` + + GatewayAddress string `yaml:"gateway_addr" env:"WEB_GATEWAY_GRPC_ADDR" desc:"GRPC address of the Reva gateway service."` + Context context.Context `yaml:"-"` } // Asset defines the available asset configuration. @@ -60,13 +63,14 @@ type Application struct { } // ExternalApp defines an external web app. -// { -// "name": "hello", -// "path": "http://localhost:9105/hello.js", -// "config": { -// "url": "http://localhost:9105" -// } -// } +// +// { +// "name": "hello", +// "path": "http://localhost:9105/hello.js", +// "config": { +// "url": "http://localhost:9105" +// } +// } type ExternalApp struct { ID string `json:"id,omitempty" yaml:"id"` Path string `json:"path,omitempty" yaml:"path"` @@ -86,3 +90,8 @@ type Web struct { ThemePath string `yaml:"theme_path" env:"WEB_UI_THEME_PATH" desc:"URL path to load themes from. The theme server will be prepended."` // used to build Theme in WebConfig Config WebConfig `yaml:"config"` } + +// TokenManager is the config for using the reva token manager +type TokenManager struct { + JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;WEB_JWT_SECRET" desc:"The secret to mint and validate jwt tokens."` +} diff --git a/services/web/pkg/config/defaults/defaultconfig.go b/services/web/pkg/config/defaults/defaultconfig.go index e4579166f..af64d6635 100644 --- a/services/web/pkg/config/defaults/defaultconfig.go +++ b/services/web/pkg/config/defaults/defaultconfig.go @@ -35,6 +35,7 @@ func DefaultConfig() *config.Config { Asset: config.Asset{ Path: filepath.Join(defaults.BaseDataPath(), "web/assets"), }, + GatewayAddress: "127.0.0.1:9142", Web: config.Web{ Path: "", ThemeServer: "https://localhost:9200", @@ -95,6 +96,13 @@ func EnsureDefaults(cfg *config.Config) { cfg.Tracing = &config.Tracing{} } + if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil { + cfg.TokenManager = &config.TokenManager{ + JWTSecret: cfg.Commons.TokenManager.JWTSecret, + } + } else if cfg.TokenManager == nil { + cfg.TokenManager = &config.TokenManager{} + } if cfg.Commons != nil { cfg.HTTP.TLS = cfg.Commons.HTTPServiceTLS } diff --git a/services/web/pkg/config/parser/parse.go b/services/web/pkg/config/parser/parse.go index 664a6f596..f90db1756 100644 --- a/services/web/pkg/config/parser/parse.go +++ b/services/web/pkg/config/parser/parse.go @@ -4,6 +4,7 @@ import ( "errors" ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" "github.com/owncloud/ocis/v2/services/web/pkg/config" "github.com/owncloud/ocis/v2/services/web/pkg/config/defaults" @@ -33,5 +34,8 @@ func ParseConfig(cfg *config.Config) error { } func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } return nil } diff --git a/services/web/pkg/server/http/server.go b/services/web/pkg/server/http/server.go index c36406407..dbe283029 100644 --- a/services/web/pkg/server/http/server.go +++ b/services/web/pkg/server/http/server.go @@ -3,6 +3,7 @@ package http import ( "fmt" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" chimiddleware "github.com/go-chi/chi/v5/middleware" "github.com/owncloud/ocis/v2/ocis-pkg/middleware" "github.com/owncloud/ocis/v2/ocis-pkg/service/http" @@ -33,9 +34,15 @@ func Server(opts ...Option) (http.Service, error) { return http.Service{}, fmt.Errorf("could not initialize http service: %w", err) } + client, err := pool.GetGatewayServiceClient(options.Config.GatewayAddress) + if err != nil { + return http.Service{}, err + } + handle := svc.NewService( svc.Logger(options.Logger), svc.Config(options.Config), + svc.GatewayClient(client), svc.Middleware( chimiddleware.RealIP, chimiddleware.RequestID, diff --git a/services/web/pkg/service/v0/branding.go b/services/web/pkg/service/v0/branding.go index 2ebe6bf5e..0b34e3e32 100644 --- a/services/web/pkg/service/v0/branding.go +++ b/services/web/pkg/service/v0/branding.go @@ -7,6 +7,10 @@ import ( "net/http" "path" "path/filepath" + + permissionsapi "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + revactx "github.com/cs3org/reva/v2/pkg/ctx" ) var ( @@ -22,6 +26,24 @@ var ( // UploadLogo implements the endpoint to upload a custom logo for the oCIS instance. func (p Web) UploadLogo(w http.ResponseWriter, r *http.Request) { + user := revactx.ContextMustGetUser(r.Context()) + rsp, err := p.gatewayClient.CheckPermission(r.Context(), &permissionsapi.CheckPermissionRequest{ + Permission: "change-logo", + SubjectRef: &permissionsapi.SubjectReference{ + Spec: &permissionsapi.SubjectReference_UserId{ + UserId: user.Id, + }, + }, + }) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + if rsp.Status.Code != rpc.Code_CODE_OK { + w.WriteHeader(http.StatusForbidden) + return + } + file, fileHeader, err := r.FormFile("logo") if err != nil { if errors.Is(err, http.ErrMissingFile) { diff --git a/services/web/pkg/service/v0/option.go b/services/web/pkg/service/v0/option.go index a7f4cd031..8a0ed024b 100644 --- a/services/web/pkg/service/v0/option.go +++ b/services/web/pkg/service/v0/option.go @@ -3,6 +3,7 @@ package svc import ( "net/http" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/web/pkg/config" ) @@ -12,9 +13,10 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Logger log.Logger - Config *config.Config - Middleware []func(http.Handler) http.Handler + Logger log.Logger + Config *config.Config + Middleware []func(http.Handler) http.Handler + GatewayClient gateway.GatewayAPIClient } // newOptions initializes the available default options. @@ -48,3 +50,10 @@ func Middleware(val ...func(http.Handler) http.Handler) Option { o.Middleware = val } } + +// GatewayClient provides a function to set the GatewayClient option. +func GatewayClient(client gateway.GatewayAPIClient) Option { + return func(o *Options) { + o.GatewayClient = client + } +} diff --git a/services/web/pkg/service/v0/service.go b/services/web/pkg/service/v0/service.go index ef151d2bb..3f816a3e5 100644 --- a/services/web/pkg/service/v0/service.go +++ b/services/web/pkg/service/v0/service.go @@ -10,9 +10,12 @@ import ( "strings" "time" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/go-chi/chi/v5" + "github.com/owncloud/ocis/v2/ocis-pkg/account" "github.com/owncloud/ocis/v2/ocis-pkg/assetsfs" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" "github.com/owncloud/ocis/v2/services/web" "github.com/owncloud/ocis/v2/services/web/pkg/assets" "github.com/owncloud/ocis/v2/services/web/pkg/config" @@ -38,14 +41,22 @@ func NewService(opts ...Option) Service { m.Use(options.Middleware...) svc := Web{ - logger: options.Logger, config: options.Config, - mux: m, - fs: assetsfs.New(web.Assets, options.Config.Asset.Path, options.Logger), + logger: options.Logger, + config: options.Config, + mux: m, + fs: assetsfs.New(web.Assets, options.Config.Asset.Path, options.Logger), + gatewayClient: options.GatewayClient, } m.Route(options.Config.HTTP.Root, func(r chi.Router) { r.Get("/config.json", svc.Config) - r.Post("/branding/logo", svc.UploadLogo) + r.Route("/branding/logo", func(r chi.Router) { + r.Use(middleware.ExtractAccountUUID( + account.Logger(options.Logger), + account.JWTSecret(options.Config.TokenManager.JWTSecret), + )) + r.Post("/", svc.UploadLogo) + }) r.Mount("/", svc.Static(options.Config.HTTP.CacheTTL)) }) @@ -59,10 +70,11 @@ func NewService(opts ...Option) Service { // Web defines implements the business logic for Service. type Web struct { - logger log.Logger - config *config.Config - mux *chi.Mux - fs *assetsfs.FileSystem + logger log.Logger + config *config.Config + mux *chi.Mux + fs *assetsfs.FileSystem + gatewayClient gateway.GatewayAPIClient } // ServeHTTP implements the Service interface.