Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ protogen/buf.sha1.lock
/third-party-licenses

# misc
.agents/
/tmp
go.work
go.work.sum
.env
.envrc
CLAUDE.md
.claude/
GEMINI.md
Comment thread
2403905 marked this conversation as resolved.
.agents/
2 changes: 0 additions & 2 deletions .make/go.mk
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ debug-linux-docker-amd64: release-dirs
-gcflags="all=-N -l" \
-tags 'netgo $(TAGS)' \
-buildmode=exe \
-trimpath \
-ldflags '-extldflags "-static" $(DEBUG_LDFLAGS) $(DOCKER_LDFLAGS)' \
-o '$(DIST)/binaries/$(EXECUTABLE)-linux-amd64' \
./cmd/$(NAME)
Comment thread
2403905 marked this conversation as resolved.
Expand All @@ -130,7 +129,6 @@ debug-linux-docker-arm64: release-dirs
-gcflags="all=-N -l" \
-tags 'netgo $(TAGS)' \
-buildmode=exe \
-trimpath \
-ldflags '-extldflags "-static" $(DEBUG_LDFLAGS) $(DOCKER_LDFLAGS)' \
-o '$(DIST)/binaries/$(EXECUTABLE)-linux-arm64' \
./cmd/$(NAME)
7 changes: 6 additions & 1 deletion deployments/examples/ocis_full/.env
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ KEYCLOAK_TRACING=
# Note: the leading colon is required to enable the service.
#KEYCLOAK=:keycloak.yml

### oCIS Vault Storage Settings ###
# Enable the oCIS vault storage
# Note: the leading colon is required to enable the service.
#VAULT_STORAGE=:vault-storage.yml


## Default Enabled Services ##

Expand Down Expand Up @@ -297,4 +302,4 @@ MAIL_SERVER_DOCKER_TAG=v1.29.3
# This MUST be the last line as it assembles the supplemental compose files to be used.
# ALL supplemental configs must be added here, whether commented or not.
# Each var must either be empty or contain :path/file.yml
COMPOSE_FILE=docker-compose.yml${OCIS:-}${TIKA:-}${S3NG:-}${S3NG_MINIO:-}${COLLABORA:-}${IMPORTER:-}${CLAMAV:-}${ONLYOFFICE:-}${EXTENSIONS:-}${UNZIP:-}${DRAWIO:-}${JSONVIEWER:-}${PROGRESSBARS:-}${EXTERNALSITES:-}${PHOTOADDON:-}${ADVANCEDSEARCH:-}${MAIL_SERVER:-}${MONITORING:-}${KEYCLOAK:-}
COMPOSE_FILE=docker-compose.yml${OCIS:-}${TIKA:-}${S3NG:-}${S3NG_MINIO:-}${COLLABORA:-}${IMPORTER:-}${CLAMAV:-}${ONLYOFFICE:-}${EXTENSIONS:-}${UNZIP:-}${DRAWIO:-}${JSONVIEWER:-}${PROGRESSBARS:-}${EXTERNALSITES:-}${PHOTOADDON:-}${ADVANCEDSEARCH:-}${MAIL_SERVER:-}${MONITORING:-}${KEYCLOAK:-}${VAULT_STORAGE:-}
37 changes: 37 additions & 0 deletions deployments/examples/ocis_full/vault-storage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
services:
ocis:
environment:
OCIS_MFA_ENABLED: true
NATS_NATS_HOST: 0.0.0.0
SETTINGS_GRPC_ADDR: ocis:9191
PROXY_CREATE_VAULT_HOME: true
GRAPH_ENABLE_VAULT_MODE: true

storage-users-vault:
image: ${OCIS_DOCKER_IMAGE:-owncloud/ocis}:${OCIS_DOCKER_TAG:-latest}
Comment thread
2403905 marked this conversation as resolved.
Outdated
networks:
ocis-net:
depends_on:
ocis:
condition: service_started
command: ["storage-users", "server"]
environment:
OCIS_LOG_LEVEL: debug
OCIS_GATEWAY_GRPC_ADDR: ocis:9142
STORAGE_USERS_ENABLE_VAULT_MODE: true
STORAGE_USERS_SERVICE_NAME: storage-users-vault
STORAGE_USERS_GRPC_ADDR: storage-users-vault:9170
STORAGE_USERS_HTTP_ADDR: storage-users-vault:9168
STORAGE_USERS_DATA_SERVER_URL: http://storage-users-vault:9168/data
STORAGE_USERS_DEBUG_ADDR: storage-users-vault:9169
STORAGE_USERS_OCIS_ROOT: /var/lib/ocis/storage/users-vault
STORAGE_USERS_EVENTS_CONSUMER_GROUP: vault-dcfs
MICRO_REGISTRY_ADDRESS: ocis:9233
OCIS_EVENTS_ENDPOINT: ocis:9233
OCIS_CACHE_STORE_NODES: ocis:9233
volumes:
- ocis-data:/var/lib/ocis
- ocis-config:/etc/ocis
Comment thread
2403905 marked this conversation as resolved.
Outdated
logging:
driver: ${LOG_DRIVER:-local}
restart: always
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ require (
github.com/open-policy-agent/opa v1.12.3
github.com/orcaman/concurrent-map v1.0.0
github.com/owncloud/libre-graph-api-go v1.0.5-0.20260216101009-eeac018af245
github.com/owncloud/reva/v2 v2.0.0-20260324082555-823c2f1c2593
github.com/owncloud/reva/v2 v2.0.0-20260422211312-0300dc8978e0
github.com/pkg/errors v0.9.1
github.com/pkg/xattr v0.4.12
github.com/prometheus/client_golang v1.23.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -742,8 +742,8 @@ github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HD
github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/owncloud/libre-graph-api-go v1.0.5-0.20260216101009-eeac018af245 h1:JRidLTAKhnvyLMRtVtSF4lhBa0NSAOs6fof+d6JnKII=
github.com/owncloud/libre-graph-api-go v1.0.5-0.20260216101009-eeac018af245/go.mod h1:z61VMGAJRtR1nbgXWiNoCkxUXP1B3Je9rMuJbnGd+Og=
github.com/owncloud/reva/v2 v2.0.0-20260324082555-823c2f1c2593 h1:RNHAod2gNBEac0KQJfJ6+PCX1t7g9hFmONTGrXFvFII=
github.com/owncloud/reva/v2 v2.0.0-20260324082555-823c2f1c2593/go.mod h1:+rCy6oGYb2/qs5gmQa8y/pHARw634vB73MZGDY2SBIQ=
github.com/owncloud/reva/v2 v2.0.0-20260422211312-0300dc8978e0 h1:hhbhzWdBfMoXKLyFRkrdEggxGD3jarE4IAt/O/QRzrA=
github.com/owncloud/reva/v2 v2.0.0-20260422211312-0300dc8978e0/go.mod h1:+rCy6oGYb2/qs5gmQa8y/pHARw634vB73MZGDY2SBIQ=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pablodz/inotifywaitgo v0.0.9 h1:njquRbBU7fuwIe5rEvtaniVBjwWzcpdUVptSgzFqZsw=
Expand Down
36 changes: 25 additions & 11 deletions services/collaboration/pkg/connector/httpadapter.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package connector

import (
"context"
"encoding/json"
"errors"
"net/http"
"strconv"

gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/owncloud/ocis/v2/ocis-pkg/mfa"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/connector/utf7"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/locks"
ctxpkg "github.com/owncloud/reva/v2/pkg/ctx"
"github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool"
"github.com/rs/zerolog"
microstore "go-micro.dev/v4/store"
"google.golang.org/grpc/metadata"
)

const (
Expand Down Expand Up @@ -67,13 +71,23 @@ func NewHttpAdapterWithConnector(con ConnectorService, l locks.LockParser) *Http
}
}

// mfaOutgoingCtx reads the MFA status from the HTTP request header and injects
// it into the outgoing gRPC metadata so that vault storage calls carry the MFA claim.
func mfaOutgoingCtx(r *http.Request) context.Context {
Comment thread
2403905 marked this conversation as resolved.
Outdated
mfaVal := "false"
if r.Header.Get(mfa.MFAHeader) == "true" {
mfaVal = "true"
}
return metadata.AppendToOutgoingContext(r.Context(), ctxpkg.MFAOutgoingHeader, mfaVal)
}

// GetLock adapts the "GetLock" operation for WOPI.
// Only the request's context is needed in order to extract the WOPI context.
// The operation's response will be sent through the response writer and
// the headers according to the spec
func (h *HttpAdapter) GetLock(w http.ResponseWriter, r *http.Request) {
fileCon := h.con.GetFileConnector()
response, err := fileCon.GetLock(r.Context())
response, err := fileCon.GetLock(mfaOutgoingCtx(r))

if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
Expand All @@ -94,7 +108,7 @@ func (h *HttpAdapter) Lock(w http.ResponseWriter, r *http.Request) {
lockID := h.locks.ParseLock(r.Header.Get(HeaderWopiLock))

fileCon := h.con.GetFileConnector()
response, err := fileCon.Lock(r.Context(), lockID, oldLockID)
response, err := fileCon.Lock(mfaOutgoingCtx(r), lockID, oldLockID)

if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
Expand All @@ -115,7 +129,7 @@ func (h *HttpAdapter) RefreshLock(w http.ResponseWriter, r *http.Request) {
lockID := h.locks.ParseLock(r.Header.Get(HeaderWopiLock))

fileCon := h.con.GetFileConnector()
response, err := fileCon.RefreshLock(r.Context(), lockID)
response, err := fileCon.RefreshLock(mfaOutgoingCtx(r), lockID)

if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
Expand All @@ -134,7 +148,7 @@ func (h *HttpAdapter) UnLock(w http.ResponseWriter, r *http.Request) {
lockID := h.locks.ParseLock(r.Header.Get(HeaderWopiLock))

fileCon := h.con.GetFileConnector()
response, err := fileCon.UnLock(r.Context(), lockID)
response, err := fileCon.UnLock(mfaOutgoingCtx(r), lockID)

if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
Expand All @@ -150,7 +164,7 @@ func (h *HttpAdapter) UnLock(w http.ResponseWriter, r *http.Request) {
// the headers according to the spec
func (h *HttpAdapter) CheckFileInfo(w http.ResponseWriter, r *http.Request) {
fileCon := h.con.GetFileConnector()
response, err := fileCon.CheckFileInfo(r.Context())
response, err := fileCon.CheckFileInfo(mfaOutgoingCtx(r))

if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
Expand All @@ -165,7 +179,7 @@ func (h *HttpAdapter) CheckFileInfo(w http.ResponseWriter, r *http.Request) {
// The file's content will be written in the response writer
func (h *HttpAdapter) GetFile(w http.ResponseWriter, r *http.Request) {
contentCon := h.con.GetContentConnector()
err := contentCon.GetFile(r.Context(), w)
err := contentCon.GetFile(mfaOutgoingCtx(r), w)
if err != nil {
var conError *ConnectorError
if errors.As(err, &conError) {
Expand All @@ -188,7 +202,7 @@ func (h *HttpAdapter) PutFile(w http.ResponseWriter, r *http.Request) {
lockID := h.locks.ParseLock(r.Header.Get(HeaderWopiLock))

contentCon := h.con.GetContentConnector()
response, err := contentCon.PutFile(r.Context(), r.Body, r.ContentLength, lockID)
response, err := contentCon.PutFile(mfaOutgoingCtx(r), r.Body, r.ContentLength, lockID)

if err != nil {
var connErr *ConnectorError
Expand Down Expand Up @@ -232,7 +246,7 @@ func (h *HttpAdapter) PutRelativeFile(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
response, putErr = fileCon.PutRelativeFileSuggested(r.Context(), h.con.GetContentConnector(), r.Body, r.ContentLength, utf8Target)
response, putErr = fileCon.PutRelativeFileSuggested(mfaOutgoingCtx(r), h.con.GetContentConnector(), r.Body, r.ContentLength, utf8Target)
}

if relativeTarget != "" {
Expand All @@ -241,7 +255,7 @@ func (h *HttpAdapter) PutRelativeFile(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
response, putErr = fileCon.PutRelativeFileRelative(r.Context(), h.con.GetContentConnector(), r.Body, r.ContentLength, utf8Target)
response, putErr = fileCon.PutRelativeFileRelative(mfaOutgoingCtx(r), h.con.GetContentConnector(), r.Body, r.ContentLength, utf8Target)
}

if putErr != nil {
Expand All @@ -263,7 +277,7 @@ func (h *HttpAdapter) DeleteFile(w http.ResponseWriter, r *http.Request) {
lockID := r.Header.Get(HeaderWopiLock)

fileCon := h.con.GetFileConnector()
response, err := fileCon.DeleteFile(r.Context(), lockID)
response, err := fileCon.DeleteFile(mfaOutgoingCtx(r), lockID)

if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
Expand Down Expand Up @@ -292,7 +306,7 @@ func (h *HttpAdapter) RenameFile(w http.ResponseWriter, r *http.Request) {
}

fileCon := h.con.GetFileConnector()
response, err := fileCon.RenameFile(r.Context(), lockID, utf8Target)
response, err := fileCon.RenameFile(mfaOutgoingCtx(r), lockID, utf8Target)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
Expand Down
8 changes: 8 additions & 0 deletions services/collaboration/pkg/middleware/wopicontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type WopiContext struct {
FileReference *providerv1beta1.Reference
TemplateReference *providerv1beta1.Reference
ViewMode appproviderv1beta1.ViewMode
HasMFA bool
}

// WopiContextAuthMiddleware will prepare an HTTP handler to be used as
Expand Down Expand Up @@ -133,6 +134,13 @@ func WopiContextAuthMiddleware(cfg *config.Config, st microstore.Store, next htt
ctx = ctxpkg.ContextSetUser(ctx, user)
ctx = ctxpkg.ContextSetScopes(ctx, scopes)

// Propagate MFA status embedded in the WOPI token to outgoing gRPC metadata.
mfaVal := "false"
if claims.WopiContext.HasMFA {
mfaVal = "true"
}
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.MFAOutgoingHeader, mfaVal)

// include additional info in the context's logger
wopiLogger = wopiLogger.With().
Str("FileReference", claims.WopiContext.FileReference.String()).
Expand Down
6 changes: 6 additions & 0 deletions services/collaboration/pkg/service/grpc/v0/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"net/url"
"path"
"slices"
"strconv"
"strings"

Expand All @@ -13,10 +14,12 @@ import (
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
ctxpkg "github.com/owncloud/reva/v2/pkg/ctx"
"github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool"
"github.com/owncloud/reva/v2/pkg/storagespace"
"github.com/owncloud/reva/v2/pkg/utils"
microstore "go-micro.dev/v4/store"
"google.golang.org/grpc/metadata"

"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
Expand Down Expand Up @@ -121,11 +124,14 @@ func (s *Service) OpenInApp(
}

// create the wopiContext and generate the token
mfav := metadata.ValueFromIncomingContext(ctx, ctxpkg.MFAOutgoingHeader)
hasMFA := slices.Contains(mfav, "true")
wopiContext := middleware.WopiContext{
AccessToken: req.GetAccessToken(), // it will be encrypted
ViewOnlyToken: utils.ReadPlainFromOpaque(req.GetOpaque(), "viewOnlyToken"),
FileReference: &providerFileRef,
ViewMode: req.GetViewMode(),
HasMFA: hasMFA,
}

if templateID := utils.ReadPlainFromOpaque(req.GetOpaque(), "template"); templateID != "" {
Expand Down
16 changes: 16 additions & 0 deletions services/gateway/pkg/revaconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,22 @@ func spacesProviders(cfg *config.Config, logger log.Logger) map[string]map[strin
},
},
},
"com.owncloud.api.storage-users-vault": {
// Use the dedicated storage provider for vault
"providerid": utils.VaultStorageProviderID,
"spaces": map[string]interface{}{
"personal": map[string]interface{}{
// The mount point must have the "vault/" prefix to be picked up by the vault storage provider
"mount_point": "/vault/users",
"path_template": "/vault/users/{{.Space.Owner.Id.OpaqueId}}",
},
"project": map[string]interface{}{
// The mount point must have the "vault/" prefix to be picked up by the vault storage provider
"mount_point": "/vault/projects",
"path_template": "/vault/projects/{{.Space.Name}}",
},
},
},
cfg.StorageSharesEndpoint: {
"providerid": utils.ShareStorageProviderID,
"spaces": map[string]interface{}{
Expand Down
2 changes: 2 additions & 0 deletions services/graph/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type Config struct {

Validation Validation `yaml:"validation"`

EnableVaultMode bool `yaml:"enable_vault_mode" env:"GRAPH_ENABLE_VAULT_MODE" desc:"Enable vault mode for the graph service runned in addition to the regular graph service. Required the running the storage-users-vault additional service." introductionVersion:"daledda"`
Comment thread
2403905 marked this conversation as resolved.
Outdated

Context context.Context `yaml:"-"`
}

Expand Down
2 changes: 1 addition & 1 deletion services/graph/pkg/config/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ package config

// Service defines the available service configuration.
type Service struct {
Name string `yaml:"-"`
Name string `yaml:"name" env:"GRAPH_SERVICE_NAME" desc:"The name of the service." introductionVersion:"daledda"`
Comment thread
2403905 marked this conversation as resolved.
Outdated
}
9 changes: 9 additions & 0 deletions services/graph/pkg/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ func Auth(opts ...account.Option) func(http.Handler) http.Handler {
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.InitiatorHeader, initiatorID)
}

// Propagate MFA status to outgoing gRPC metadata so that services
// protected by the mfa interceptor (e.g. storage-users-vault)
// can enforce MFA at the gRPC layer.
mfaVal := "false"
if mfa.Has(ctx) {
mfaVal = "true"
}
ctx = metadata.AppendToOutgoingContext(ctx, revactx.MFAOutgoingHeader, mfaVal)

next.ServeHTTP(w, r.WithContext(ctx))
})
}
Expand Down
23 changes: 23 additions & 0 deletions services/graph/pkg/middleware/mfa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package middleware

import (
"net/http"

"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/ocis-pkg/mfa"
)

// RequireMFA middleware is used to require the user in context to have MFA satisfied
func RequireMFA(logger log.Logger) func(next http.Handler) http.Handler {
Comment thread
2403905 marked this conversation as resolved.
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !mfa.Has(r.Context()) {
l := logger.SubloggerWithRequestID(r.Context())
l.Error().Str("path", r.URL.Path).Msg("MFA required but not satisfied")
mfa.SetRequiredStatus(w)
return
}
next.ServeHTTP(w, r)
})
}
}
Loading