+2
-2
@@ -798,7 +798,7 @@ def wopiValidatorTests(ctx, storage, accounts_hash_difficulty = 4):
|
||||
},
|
||||
{
|
||||
"name": "wopiserver",
|
||||
"image": "cs3org/wopiserver:v9.4.0",
|
||||
"image": "cs3org/wopiserver:v9.4.1",
|
||||
"detach": True,
|
||||
"commands": [
|
||||
"cp %s/tests/config/drone/wopiserver.conf /etc/wopi/wopiserver.conf" % (dirs["base"]),
|
||||
@@ -810,7 +810,7 @@ def wopiValidatorTests(ctx, storage, accounts_hash_difficulty = 4):
|
||||
"name": "wait-for-wopi-server",
|
||||
"image": OC_CI_ALPINE,
|
||||
"commands": [
|
||||
"curl -k --fail --retry-connrefused --retry 7 --retry-all-errors 'http://wopiserver:8880/wopi'",
|
||||
"curl -k --fail --retry-connrefused --retry 9 --retry-all-errors 'http://wopiserver:8880/wopi'",
|
||||
],
|
||||
},
|
||||
] +
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Enhancement: Add endpoints to upload a custom logo
|
||||
|
||||
Added endpoints to upload and reset custom logos. The files are stored under the `WEB_ASSET_PATH`
|
||||
which defaults to `$OCIS_BASE_DATA_PATH/web/assets`.
|
||||
|
||||
https://github.com/owncloud/ocis/pull/5559
|
||||
@@ -1,12 +1,11 @@
|
||||
package assetsfs
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
)
|
||||
@@ -21,22 +20,35 @@ type FileSystem struct {
|
||||
// Open checks if assetPath is set and tries to load from there. Falls back to fs if that is not possible
|
||||
func (f *FileSystem) Open(original string) (http.File, error) {
|
||||
if f.assetPath != "" {
|
||||
file, err := read(f.assetPath, original)
|
||||
file, err := os.Open(filepath.Join(f.assetPath, original))
|
||||
if err == nil {
|
||||
return file, nil
|
||||
}
|
||||
f.log.Warn().
|
||||
Str("path", f.assetPath).
|
||||
Str("filename", original).
|
||||
Str("error", err.Error()).
|
||||
Msg("error reading from assetPath")
|
||||
}
|
||||
|
||||
return f.fs.Open(original)
|
||||
}
|
||||
|
||||
func (f *FileSystem) OpenEmbedded(name string) (http.File, error) {
|
||||
return f.fs.Open(name)
|
||||
}
|
||||
|
||||
// Create creates a new file in the assetPath
|
||||
func (f *FileSystem) Create(name string) (*os.File, error) {
|
||||
fullPath := f.jailPath(name)
|
||||
if err := os.MkdirAll(filepath.Dir(fullPath), 0770); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.Create(fullPath)
|
||||
}
|
||||
|
||||
// jailPath returns the fullPath `<assetPath>/<name>`. It makes sure that the path is
|
||||
// always under `<assetPath>` to prevent directory traversal.
|
||||
func (f *FileSystem) jailPath(name string) string {
|
||||
return filepath.Join(f.assetPath, filepath.Join("/", name))
|
||||
}
|
||||
|
||||
// New initializes a new FileSystem. Quits on error
|
||||
func New(embedFS embed.FS, assetPath string, logger log.Logger) *FileSystem {
|
||||
func New(embedFS fs.FS, assetPath string, logger log.Logger) *FileSystem {
|
||||
f, err := fs.Sub(embedFS, "assets")
|
||||
if err != nil {
|
||||
fmt.Println("Cannot load subtree fs:", err.Error())
|
||||
@@ -49,17 +61,3 @@ func New(embedFS embed.FS, assetPath string, logger log.Logger) *FileSystem {
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// tries to read file from disk or errors
|
||||
func read(assetPath string, fileName string) (http.File, error) {
|
||||
if stat, err := os.Stat(assetPath); err != nil || !stat.IsDir() {
|
||||
return nil, fmt.Errorf("can't load asset path: %s", err)
|
||||
}
|
||||
|
||||
p := path.Join(assetPath, fileName)
|
||||
if _, err := os.Stat(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return os.Open(p)
|
||||
}
|
||||
|
||||
@@ -83,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",
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/assetsfs"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/services/web"
|
||||
"github.com/owncloud/ocis/v2/services/web/pkg/config"
|
||||
)
|
||||
|
||||
// New returns a new http filesystem to serve assets.
|
||||
func New(opts ...Option) http.FileSystem {
|
||||
options := newOptions(opts...)
|
||||
return assetsfs.New(web.Assets, options.Config.Asset.Path, options.Logger)
|
||||
}
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Config *config.Config
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(val log.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = val
|
||||
}
|
||||
}
|
||||
|
||||
// Config provides a function to set the config option.
|
||||
func Config(val *config.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.Config = val
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,13 @@ package assets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"golang.org/x/net/html"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
type fileServer struct {
|
||||
|
||||
@@ -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:"The bind address of the GRPC 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."`
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/config/defaults"
|
||||
"github.com/owncloud/ocis/v2/services/web/pkg/config"
|
||||
)
|
||||
|
||||
@@ -31,8 +33,9 @@ func DefaultConfig() *config.Config {
|
||||
Name: "web",
|
||||
},
|
||||
Asset: config.Asset{
|
||||
Path: "",
|
||||
Path: filepath.Join(defaults.BaseDataPath(), "web/assets"),
|
||||
},
|
||||
GatewayAddress: "127.0.0.1:9142",
|
||||
Web: config.Web{
|
||||
Path: "",
|
||||
ThemeServer: "https://localhost:9200",
|
||||
@@ -93,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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"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 (
|
||||
errInvalidThemeConfig = errors.New("invalid themes config")
|
||||
_themesConfigPath = filepath.FromSlash("themes/owncloud/theme.json")
|
||||
_allowedExtensionMediatypes = map[string]string{
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".png": "image/png",
|
||||
".gif": "image/gif",
|
||||
}
|
||||
)
|
||||
|
||||
// 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) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
mediatype := fileHeader.Header.Get("Content-Type")
|
||||
if !allowedFiletype(fileHeader.Filename, mediatype) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
fp := filepath.Join("branding", filepath.Join("/", fileHeader.Filename))
|
||||
err = p.storeAsset(fp, file)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = p.updateLogoThemeConfig(fp)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// ResetLogo implements the endpoint to reset the instance logo.
|
||||
// The config will be changed back to use the embedded logo asset.
|
||||
func (p Web) ResetLogo(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
|
||||
}
|
||||
|
||||
f, err := p.fs.OpenEmbedded(_themesConfigPath)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
originalPath, err := p.getLogoPath(f)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := p.updateLogoThemeConfig(originalPath); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p Web) storeAsset(name string, asset io.Reader) error {
|
||||
dst, err := p.fs.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
_, err = io.Copy(dst, asset)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p Web) getLogoPath(r io.Reader) (string, error) {
|
||||
// This decoding of the themes.json file is not optimal. If we need to decode it for other
|
||||
// usecases as well we should consider decoding to a struct.
|
||||
var m map[string]interface{}
|
||||
_ = json.NewDecoder(r).Decode(&m)
|
||||
|
||||
webCfg, ok := m["web"].(map[string]interface{})
|
||||
if !ok {
|
||||
return "", errInvalidThemeConfig
|
||||
}
|
||||
|
||||
defaultCfg, ok := webCfg["default"].(map[string]interface{})
|
||||
if !ok {
|
||||
return "", errInvalidThemeConfig
|
||||
}
|
||||
|
||||
logoCfg, ok := defaultCfg["logo"].(map[string]interface{})
|
||||
if !ok {
|
||||
return "", errInvalidThemeConfig
|
||||
}
|
||||
|
||||
logoPath, ok := logoCfg["login"].(string)
|
||||
if !ok {
|
||||
return "", errInvalidThemeConfig
|
||||
}
|
||||
|
||||
return logoPath, nil
|
||||
}
|
||||
|
||||
func (p Web) updateLogoThemeConfig(logoPath string) error {
|
||||
f, err := p.fs.Open(_themesConfigPath)
|
||||
if err == nil {
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
// This decoding of the themes.json file is not optimal. If we need to decode it for other
|
||||
// usecases as well we should consider decoding to a struct.
|
||||
var m map[string]interface{}
|
||||
_ = json.NewDecoder(f).Decode(&m)
|
||||
|
||||
webCfg, ok := m["web"].(map[string]interface{})
|
||||
if !ok {
|
||||
return errInvalidThemeConfig
|
||||
}
|
||||
|
||||
defaultCfg, ok := webCfg["default"].(map[string]interface{})
|
||||
if !ok {
|
||||
return errInvalidThemeConfig
|
||||
}
|
||||
|
||||
logoCfg, ok := defaultCfg["logo"].(map[string]interface{})
|
||||
if !ok {
|
||||
return errInvalidThemeConfig
|
||||
}
|
||||
|
||||
logoCfg["login"] = logoPath
|
||||
logoCfg["topbar"] = logoPath
|
||||
|
||||
defaultDarkCfg, ok := webCfg["default-dark"].(map[string]interface{})
|
||||
if !ok {
|
||||
return errInvalidThemeConfig
|
||||
}
|
||||
|
||||
logoDarkCfg, ok := defaultDarkCfg["logo"].(map[string]interface{})
|
||||
if !ok {
|
||||
return errInvalidThemeConfig
|
||||
}
|
||||
|
||||
logoDarkCfg["login"] = logoPath
|
||||
logoDarkCfg["topbar"] = logoPath
|
||||
|
||||
dst, err := p.fs.Create(_themesConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.NewEncoder(dst).Encode(m)
|
||||
}
|
||||
|
||||
func allowedFiletype(filename, mediatype string) bool {
|
||||
ext := path.Ext(filename)
|
||||
|
||||
// Check if we allow that extension and if the mediatype matches the extension
|
||||
mt, ok := _allowedExtensionMediatypes[ext]
|
||||
return ok && mt == mediatype
|
||||
}
|
||||
@@ -28,3 +28,13 @@ func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (i instrument) Config(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.Config(w, r)
|
||||
}
|
||||
|
||||
// UploadLogo implements the Service interface.
|
||||
func (i instrument) UploadLogo(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.UploadLogo(w, r)
|
||||
}
|
||||
|
||||
// ResetLogo implements the Service interface.
|
||||
func (i instrument) ResetLogo(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.ResetLogo(w, r)
|
||||
}
|
||||
|
||||
@@ -28,3 +28,13 @@ func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (l logging) Config(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.Config(w, r)
|
||||
}
|
||||
|
||||
// UploadLogo implements the Service interface.
|
||||
func (l logging) UploadLogo(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.UploadLogo(w, r)
|
||||
}
|
||||
|
||||
// ResetLogo implements the Service interface.
|
||||
func (l logging) ResetLogo(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.ResetLogo(w, r)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,13 @@ 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"
|
||||
)
|
||||
@@ -25,6 +30,8 @@ var (
|
||||
type Service interface {
|
||||
ServeHTTP(http.ResponseWriter, *http.Request)
|
||||
Config(http.ResponseWriter, *http.Request)
|
||||
UploadLogo(http.ResponseWriter, *http.Request)
|
||||
ResetLogo(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
// NewService returns a service implementation for Service.
|
||||
@@ -35,13 +42,23 @@ func NewService(opts ...Option) Service {
|
||||
m.Use(options.Middleware...)
|
||||
|
||||
svc := Web{
|
||||
logger: options.Logger,
|
||||
config: options.Config,
|
||||
mux: m,
|
||||
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.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.Delete("/", svc.ResetLogo)
|
||||
})
|
||||
r.Mount("/", svc.Static(options.Config.HTTP.CacheTTL))
|
||||
})
|
||||
|
||||
@@ -55,9 +72,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
|
||||
logger log.Logger
|
||||
config *config.Config
|
||||
mux *chi.Mux
|
||||
fs *assetsfs.FileSystem
|
||||
gatewayClient gateway.GatewayAPIClient
|
||||
}
|
||||
|
||||
// ServeHTTP implements the Service interface.
|
||||
@@ -131,12 +150,7 @@ func (p Web) Static(ttl int) http.HandlerFunc {
|
||||
|
||||
static := http.StripPrefix(
|
||||
rootWithSlash,
|
||||
assets.FileServer(
|
||||
assets.New(
|
||||
assets.Logger(p.logger),
|
||||
assets.Config(p.config),
|
||||
),
|
||||
),
|
||||
assets.FileServer(p.fs),
|
||||
)
|
||||
|
||||
lastModified := time.Now().UTC().Format(http.TimeFormat)
|
||||
|
||||
@@ -24,3 +24,13 @@ func (t tracing) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (t tracing) Config(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.Config(w, r)
|
||||
}
|
||||
|
||||
// UploadLogo implements the Service interface.
|
||||
func (t tracing) UploadLogo(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.UploadLogo(w, r)
|
||||
}
|
||||
|
||||
// ResetLogo implements the Service interface.
|
||||
func (t tracing) ResetLogo(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.ResetLogo(w, r)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user