Merge pull request #9165 from wkloucek/collaboration-fixes

collaboration fixes
This commit is contained in:
Jörn Friedrich Dreyer
2024-05-31 13:49:10 +02:00
committed by GitHub
23 changed files with 159 additions and 79 deletions
+8 -10
View File
@@ -71,10 +71,7 @@ func Server(cfg *config.Config) *cli.Command {
)
defer teardown()
if err != nil {
logger.Info().
Err(err).
Str("transport", "grpc").
Msg("Failed to initialize server")
logger.Error().Err(err).Str("transport", "grpc").Msg("Failed to initialize server")
return err
}
@@ -85,11 +82,8 @@ func Server(cfg *config.Config) *cli.Command {
}
return grpcServer.Serve(l)
},
func(_ error) {
logger.Error().
Err(err).
Str("server", "grpc").
Msg("shutting down server")
func(err error) {
logger.Error().Err(err).Str("server", "grpc").Msg("shutting down server")
cancel()
})
@@ -100,7 +94,7 @@ func Server(cfg *config.Config) *cli.Command {
debug.Config(cfg),
)
if err != nil {
logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server")
logger.Error().Err(err).Str("transport", "debug").Msg("Failed to initialize server")
return err
}
@@ -117,6 +111,10 @@ func Server(cfg *config.Config) *cli.Command {
http.Context(ctx),
http.TracerProvider(traceProvider),
)
if err != nil {
logger.Error().Err(err).Str("transport", "http").Msg("Failed to initialize server")
return err
}
gr.Add(httpServer.Run, func(_ error) {
cancel()
})
+3
View File
@@ -6,4 +6,7 @@ type App struct {
Description string `yaml:"description" env:"COLLABORATION_APP_DESCRIPTION" desc:"App description" introductionVersion:"5.1"`
Icon string `yaml:"icon" env:"COLLABORATION_APP_ICON" desc:"Icon for the app" introductionVersion:"5.1"`
LockName string `yaml:"lockname" env:"COLLABORATION_APP_LOCKNAME" desc:"Name for the app lock" introductionVersion:"5.1"`
Addr string `yaml:"addr" env:"COLLABORATION_APP_ADDR" desc:"The URL where the WOPI app is located, such as https://127.0.0.1:8080." introductionVersion:"5.1"`
Insecure bool `yaml:"insecure" env:"COLLABORATION_APP_INSECURE" desc:"Skip TLS certificate verification when connecting to the WOPI app" introductionVersion:"5.1"`
}
+6 -5
View File
@@ -14,12 +14,13 @@ type Config struct {
Service Service `yaml:"-"`
App App `yaml:"app"`
JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;COLLABORATION_JWT_SECRET" desc:"Used as JWT token and to encrypt access token." introductionVersion:"5.1"`
TokenManager *TokenManager `yaml:"token_manager"`
GRPC GRPC `yaml:"grpc"`
HTTP HTTP `yaml:"http"`
WopiApp WopiApp `yaml:"wopiapp"`
CS3Api CS3Api `yaml:"cs3api"`
GRPC GRPC `yaml:"grpc"`
HTTP HTTP `yaml:"http"`
Wopi Wopi `yaml:"wopi"`
CS3Api CS3Api `yaml:"cs3api"`
Tracing *Tracing `yaml:"tracing"`
Log *Log `yaml:"log"`
+1 -1
View File
@@ -8,7 +8,7 @@ type CS3Api struct {
// Gateway defines the available configuration for the CS3 API gateway
type Gateway struct {
Name string `yaml: "name" env:"OCIS_REVA_GATEWAY;COLLABORATION_CS3API_GATEWAY_NAME" desc:"The service name of the CS3API gateway." introductionVersion:"5.1"`
Name string `yaml:"name" env:"OCIS_REVA_GATEWAY;COLLABORATION_CS3API_GATEWAY_NAME" desc:"The service name of the CS3API gateway." introductionVersion:"5.1"`
}
// DataGateway defines the available configuration for the CS3 API data gateway
@@ -1,7 +1,7 @@
package defaults
import (
"github.com/owncloud/ocis/v2/ocis-pkg/generators"
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
)
@@ -15,7 +15,6 @@ func FullDefaultConfig() *config.Config {
// DefaultConfig returns a basic default configuration
func DefaultConfig() *config.Config {
secret, _ := generators.GenerateRandomString(generators.AlphaNumChars, 32) // anything to do with the error?
return &config.Config{
Service: config.Service{
Name: "collaboration",
@@ -25,17 +24,16 @@ func DefaultConfig() *config.Config {
Description: "Open office documents with Collabora",
Icon: "image-edit",
LockName: "com.github.owncloud.collaboration",
Addr: "https://127.0.0.1:9980",
Insecure: false,
},
JWTSecret: secret,
GRPC: config.GRPC{
Addr: "0.0.0.0:9301",
Namespace: "com.owncloud.collaboration",
Addr: "127.0.0.1:9301",
Namespace: "com.owncloud.api",
},
HTTP: config.HTTP{
Addr: "127.0.0.1:9300",
BindAddr: "0.0.0.0:9300",
Namespace: "com.owncloud.collaboration",
Scheme: "https",
Namespace: "com.owncloud.web",
},
Debug: config.Debug{
Addr: "127.0.0.1:9304",
@@ -43,13 +41,12 @@ func DefaultConfig() *config.Config {
Pprof: false,
Zpages: false,
},
WopiApp: config.WopiApp{
Addr: "https://127.0.0.1:8080",
Insecure: false,
Wopi: config.Wopi{
WopiSrc: "https://localhost:9300",
},
CS3Api: config.CS3Api{
Gateway: config.Gateway{
Name: "com.owncloud.api.gateway",
Name: shared.DefaultRevaConfig().Address,
},
DataGateway: config.DataGateway{
Insecure: false,
@@ -83,6 +80,14 @@ func EnsureDefaults(cfg *config.Config) {
} else if cfg.Tracing == nil {
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{}
}
}
// Sanitize sanitized the configuration
+1 -3
View File
@@ -6,9 +6,7 @@ import (
// HTTP defines the available http configuration.
type HTTP struct {
Addr string `yaml:"addr" env:"COLLABORATION_HTTP_ADDR" desc:"The external address of the collaboration service wihout a leading scheme. Either use an IP address or a hostname (127.0.0.1:9301 or wopi.private.prv). The configured 'Scheme' in another envvar will be used to finally build the public URL along with this address." introductionVersion:"5.1"`
BindAddr string `yaml:"bindaddr" env:"COLLABORATION_HTTP_BINDADDR" desc:"The bind address of the HTTP service. Use '<ip-address>:<port>', for example, '127.0.0.1:9301' or '0.0.0.0:9301'." introductionVersion:"5.1"`
Addr string `yaml:"addr" env:"COLLABORATION_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"5.1"`
Namespace string `yaml:"-"`
Scheme string `yaml:"scheme" env:"COLLABORATION_HTTP_SCHEME" desc:"The scheme to use for the HTTP address, which is either 'http' or 'https'." introductionVersion:"5.1"`
TLS shared.HTTPServiceTLS `yaml:"tls"`
}
@@ -2,12 +2,15 @@ package parser
import (
"errors"
"fmt"
"net/url"
ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config"
ocisdefaults "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults"
"github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode"
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config/defaults"
"github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode"
)
// ParseConfig loads configuration from known paths.
@@ -34,5 +37,27 @@ func ParseConfig(cfg *config.Config) error {
// Validate validates the configuration
func Validate(cfg *config.Config) error {
if cfg.TokenManager.JWTSecret == "" {
return shared.MissingJWTTokenError(cfg.Service.Name)
}
if cfg.Wopi.Secret == "" {
return shared.MissingWOPISecretError(cfg.Service.Name)
}
url, err := url.Parse(cfg.Wopi.WopiSrc)
if err != nil {
return fmt.Errorf("The WOPI Src has not been set properly in your config for %s. "+
"Make sure your %s config contains the proper values "+
"(e.g. by running ocis init or setting it manually in "+
"the config/corresponding environment variable): %s",
cfg.Service.Name, ocisdefaults.BaseConfigPath(), err.Error())
}
if url.Path != "" {
return fmt.Errorf("The WOPI Src must not contain a path in your config for %s. "+
"Make sure your %s config contains the proper values "+
"(e.g. by running ocis init or setting it manually in "+
"the config/corresponding environment variable)",
cfg.Service.Name, ocisdefaults.BaseConfigPath())
}
return nil
}
@@ -0,0 +1,6 @@
package config
// TokenManager is the config for using the reva token manager
type TokenManager struct {
JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;COLLABORATION_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"pre5.0"`
}
@@ -0,0 +1,7 @@
package config
// Wopi defines the available configuration for the WOPI endpoint.
type Wopi struct {
WopiSrc string `yaml:"wopisrc" env:"COLLABORATION_WOPI_SRC" desc:"The WOPISrc base URL containing schema, host and port. Set this to the schema and domain where the collaboration service is reachable for the wopi app, such as https://office.owncloud.test." introductionVersion:"5.1"`
Secret string `yaml:"secret" env:"COLLABORATION_WOPI_SECRET" desc:"Used to mint and verify WOPI JWT tokens and encrypt and decrypt the REVA JWT token embedded in the WOPI JWT token." introductionVersion:"5.1"`
}
@@ -1,7 +0,0 @@
package config
// WopiApp defines the available configuration in order to connect to a WOPI app.
type WopiApp struct {
Addr string `yaml:"addr" env:"COLLABORATION_WOPIAPP_ADDR" desc:"The URL where the WOPI app is located, such as https://127.0.0.1:8080." introductionVersion:"5.1"`
Insecure bool `yaml:"insecure" env:"COLLABORATION_WOPIAPP_INSECURE" desc:"Skip TLS certificate verification when connecting to the WOPI app" introductionVersion:"5.1"`
}
@@ -17,12 +17,12 @@ import (
// target WOPI app (onlyoffice, collabora, etc) via their "/hosting/discovery"
// endpoint.
func GetAppURLs(cfg *config.Config, logger log.Logger) (map[string]map[string]string, error) {
wopiAppUrl := cfg.WopiApp.Addr + "/hosting/discovery"
wopiAppUrl := cfg.App.Addr + "/hosting/discovery"
httpClient := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: cfg.WopiApp.Insecure,
InsecureSkipVerify: cfg.App.Insecure,
},
},
}
@@ -67,7 +67,7 @@ var _ = Describe("Discovery", func() {
Describe("GetAppURLs", func() {
It("Good discovery URL", func() {
cfg := &config.Config{
WopiApp: config.WopiApp{
App: config.App{
Addr: srv.URL + "/good",
Insecure: true,
},
@@ -95,7 +95,7 @@ var _ = Describe("Discovery", func() {
It("Wrong discovery URL", func() {
cfg := &config.Config{
WopiApp: config.WopiApp{
App: config.App{
Addr: srv.URL + "/bad",
Insecure: true,
},
@@ -109,7 +109,7 @@ var _ = Describe("Discovery", func() {
It("Not XML formatted", func() {
cfg := &config.Config{
WopiApp: config.WopiApp{
App: config.App{
Addr: srv.URL + "/wrongformat",
Insecure: true,
},
@@ -11,6 +11,7 @@ import (
"github.com/gofrs/uuid"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
"github.com/owncloud/ocis/v2/ocis-pkg/version"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
)
@@ -18,7 +19,7 @@ import (
// There are no explicit requirements for the context, and it will be passed
// without changes to the underlying RegisterService method.
func RegisterOcisService(ctx context.Context, cfg *config.Config, logger log.Logger) error {
svc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, "0.0.0")
svc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString())
return registry.RegisterService(ctx, svc, logger)
}
@@ -25,10 +25,11 @@ func Server(opts ...Option) (http.Service, error) {
http.TLSConfig(options.Config.HTTP.TLS),
http.Logger(options.Logger),
http.Namespace(options.Config.HTTP.Namespace),
http.Name("wopi"),
http.Name(options.Config.Service.Name),
http.Version(version.GetString()),
http.Address(options.Config.HTTP.BindAddr),
http.Address(options.Config.HTTP.Addr),
http.Context(options.Context),
http.TraceProvider(options.TracerProvider),
)
if err != nil {
options.Logger.Error().
@@ -40,7 +41,7 @@ func Server(opts ...Option) (http.Service, error) {
middlewares := []func(stdhttp.Handler) stdhttp.Handler{
chimiddleware.RequestID,
middleware.Version(
"userlog",
options.Config.Service.Name,
version.GetString(),
),
middleware.Logger(
@@ -48,7 +49,7 @@ func Server(opts ...Option) (http.Service, error) {
),
middleware.ExtractAccountUUID(
account.Logger(options.Logger),
account.JWTSecret(options.Config.JWTSecret), // previously, secret came from Config.TokenManager.JWTSecret
account.JWTSecret(options.Config.TokenManager.JWTSecret),
),
/*
// Need CORS? not in the original server
@@ -68,7 +69,7 @@ func Server(opts ...Option) (http.Service, error) {
mux.Use(
otelchi.Middleware(
"collaboration",
options.Config.Service.Name,
otelchi.WithChiRoutes(mux),
otelchi.WithTracerProvider(options.TracerProvider),
otelchi.WithPropagators(tracing.GetPropagator()),
@@ -77,6 +78,12 @@ func Server(opts ...Option) (http.Service, error) {
prepareRoutes(mux, options)
// in debug mode print out the actual routes
_ = chi.Walk(mux, func(method string, route string, handler stdhttp.Handler, middlewares ...func(stdhttp.Handler) stdhttp.Handler) error {
options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
return nil
})
if err := micro.RegisterHandler(service.Server(), mux); err != nil {
return http.Service{}, err
}
@@ -110,7 +117,7 @@ func prepareRoutes(r *chi.Mux, options Options) {
r.Use(func(h stdhttp.Handler) stdhttp.Handler {
// authentication and wopi context
return colabmiddleware.WopiContextAuthMiddleware(options.Config.JWTSecret, h)
return colabmiddleware.WopiContextAuthMiddleware(options.Config.Wopi.Secret, h)
},
)
@@ -123,11 +123,11 @@ func (s *Service) OpenInApp(
viewAppURL = editAppURL
}
wopiSrcURL := url.URL{
Scheme: s.config.HTTP.Scheme,
Host: s.config.HTTP.Addr,
Path: path.Join("wopi", "files", fileRef),
wopiSrcURL, err := url.Parse(s.config.Wopi.WopiSrc)
if err != nil {
return nil, err
}
wopiSrcURL.Path = path.Join("wopi", "files", fileRef)
addWopiSrcQueryParam := func(baseURL string) (string, error) {
u, err := url.Parse(baseURL)
@@ -169,7 +169,7 @@ func (s *Service) OpenInApp(
appURL = editAppURL
}
cryptedReqAccessToken, err := middleware.EncryptAES([]byte(s.config.JWTSecret), req.GetAccessToken())
cryptedReqAccessToken, err := middleware.EncryptAES([]byte(s.config.Wopi.Secret), req.GetAccessToken())
if err != nil {
s.logger.Error().
Err(err).
@@ -184,7 +184,7 @@ func (s *Service) OpenInApp(
wopiContext := middleware.WopiContext{
AccessToken: cryptedReqAccessToken,
ViewOnlyToken: utils.ReadPlainFromOpaque(req.Opaque, "viewOnlyToken"),
ViewOnlyToken: utils.ReadPlainFromOpaque(req.GetOpaque(), "viewOnlyToken"),
FileReference: providerFileRef,
User: user,
ViewMode: req.GetViewMode(),
@@ -213,7 +213,7 @@ func (s *Service) OpenInApp(
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
accessToken, err := token.SignedString([]byte(s.config.JWTSecret))
accessToken, err := token.SignedString([]byte(s.config.Wopi.Secret))
if err != nil {
s.logger.Error().
@@ -7,7 +7,7 @@ import (
. "github.com/onsi/gomega"
)
func TestGraph(t *testing.T) {
func TestService(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Service Suite")
}
@@ -103,8 +103,7 @@ var _ = Describe("Discovery", func() {
It("Invalid access token", func() {
ctx := context.Background()
cfg.HTTP.Addr = "wopiserver.test.prv"
cfg.HTTP.Scheme = "https"
cfg.Wopi.WopiSrc = "https://wopiserver.test.prv"
req := &appproviderv1beta1.OpenInAppRequest{
ResourceInfo: &providerv1beta1.ResourceInfo{
@@ -140,9 +139,8 @@ var _ = Describe("Discovery", func() {
ctx := context.Background()
nowTime := time.Now()
cfg.HTTP.Addr = "wopiserver.test.prv"
cfg.HTTP.Scheme = "https"
cfg.JWTSecret = "my_supa_secret"
cfg.Wopi.WopiSrc = "https://wopiserver.test.prv"
cfg.Wopi.Secret = "my_supa_secret"
myself := &userv1beta1.User{
Id: &userv1beta1.UserId{
@@ -163,7 +161,7 @@ var _ = Describe("Discovery", func() {
Path: "/path/to/file.docx",
},
ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE,
AccessToken: MintToken(myself, cfg.JWTSecret, nowTime),
AccessToken: MintToken(myself, cfg.Wopi.Secret, nowTime),
}
gatewayClient.On("WhoAmI", mock.Anything, mock.Anything).Times(1).Return(&gatewayv1beta1.WhoAmIResponse{