add a permission check to the logo upload

This commit is contained in:
David Christofas
2023-02-09 16:18:44 +01:00
parent 13dfc06f63
commit 7c17ddb0b0
10 changed files with 137 additions and 25 deletions

View File

@@ -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",
},
},
},
}

View File

@@ -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{

View File

@@ -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,
},
},
},
},
}
}

View File

@@ -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."`
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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
}
}

View File

@@ -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.