Skip to content

[full-ci] feat: [OCISDEV-533] Provide the protected-* spaces#12069

Closed
2403905 wants to merge 5 commits intomasterfrom
feat/OCISDEV-533
Closed

[full-ci] feat: [OCISDEV-533] Provide the protected-* spaces#12069
2403905 wants to merge 5 commits intomasterfrom
feat/OCISDEV-533

Conversation

@2403905
Copy link
Copy Markdown
Contributor

@2403905 2403905 commented Feb 27, 2026

Description

The protected-personal space will be created automatically

To create the protected-project use:

curl -kv 'https://ocis.owncloud.works/graph/v1beta1/drives?template=default' \
-H 'Accept: application/json' \
--data-raw '{"name":"New pro","driveType": "protected-project"}' \
-H 'accept: application/json' \
-H 'authorization: Bearer <token>'

The graph/v1beta1/me/drives

curl -kv 'https://ocis.owncloud.works/graph/v1beta1/me/drives?%24orderby=name+asc&%24filter=driveType+eq+%27protected-personal%27' \
-H 'accept: application/json' \
-H 'authorization: Bearer <token>'
curl -kv 'https://ocis.owncloud.works/graph/v1beta1/me/drives?%24orderby=name+asc&%24filter=driveType+eq+%27protected-project%27' \
-H 'accept: application/json' \
-H 'authorization: Bearer <token>'

Acceptance Criteria

New space types: protected-personal and protected-project
All users get a protected-personal space
The protected-project spaces can be created via API
Accessing (WebDAV, Downloading, Archiver) resources on protected spaces requires MFA

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Technical debt
  • Tests only (no source changes)

Checklist:

  • Code changes
  • Unit tests added
  • Acceptance tests added
  • Documentation ticket raised:

@update-docs
Copy link
Copy Markdown

update-docs bot commented Feb 27, 2026

Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would create a changelog item based on your changes.

@2403905 2403905 force-pushed the feat/OCISDEV-533 branch 2 times, most recently from a15017d to 538b3f5 Compare March 3, 2026 16:32
@2403905 2403905 force-pushed the feat/OCISDEV-533 branch from bbde382 to b44091a Compare March 4, 2026 15:20
@2403905 2403905 marked this pull request as ready for review March 5, 2026 08:34
@2403905 2403905 changed the title Feat/ocisdev 533 [full-ci] feat: [OCISDEV-533] Provide the protected-* spaces Mar 5, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces new “protected” storage space types (protected-personal, protected-project) and adds MFA-based access restrictions around them across the stack (reva/WebDAV/archiver + oCIS Graph/search/proxy + gateway config).

Changes:

  • Bump github.com/owncloud/reva/v2 and extend reva space handling/config to include protected space types.
  • Enforce MFA for accessing protected spaces in WebDAV (and archiver) and prevent protected→unprotected copy operations.
  • Add automatic creation for protected-personal on home creation, and update Graph/search behavior for protected spaces.

Reviewed changes

Copilot reviewed 9 out of 22 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
vendor/modules.txt Updates vendored module list for the reva bump.
vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/spaces.go Adds protected space types to decomposedfs space creation/listing/deletion logic.
vendor/github.com/owncloud/reva/v2/pkg/storage/utils/decomposedfs/permissions/spacepermissions.go Extends quota permission checks to protected space types.
vendor/github.com/owncloud/reva/v2/pkg/storage/registry/spaces/spaces.go Adds default registry mapping for protected space mountpoints/path templates.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocdav/spaces.go Adds MFA enforcement when addressing a space by ID.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocdav/get.go Enforces MFA when downloading from protected spaces.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocdav/dav.go Introduces protected/unprotected helpers + MFA header constants for ocdav.
vendor/github.com/owncloud/reva/v2/internal/http/services/owncloud/ocdav/copy.go Adds copy restriction from protected→unprotected spaces.
vendor/github.com/owncloud/reva/v2/internal/http/services/archiver/handler.go Adds MFA enforcement before allowing archive downloads of protected space resources.
vendor/github.com/owncloud/reva/v2/internal/grpc/services/storageprovider/storageprovider.go Improves status propagation when CreateHome fails.
vendor/github.com/owncloud/reva/v2/internal/grpc/services/gateway/storageprovidercache.go Ensures cached CreateHome/CreateStorageSpace responses return ALREADY_EXISTS status code.
services/search/pkg/search/service.go Excludes protected spaces from search scope.
services/proxy/pkg/middleware/create_home.go Attempts to create protected-personal after successful home creation.
services/graph/pkg/service/v0/users.go Refactors query/MFA validation usage for users listing.
services/graph/pkg/service/v0/groups.go Refactors query/MFA validation usage for groups listing.
services/graph/pkg/service/v0/graph.go Introduces reusable query/MFA validators and driveType MFA gating.
services/graph/pkg/service/v0/drives.go Adds protected space types to drives handling and filters protected spaces when MFA is missing.
services/graph/pkg/service/v0/graph_test.go Adds tests for protected space filtering and protected-project creation.
services/gateway/pkg/revaconfig/config.go Adds protected space registry config for the gateway-generated reva config.
go.mod / go.sum Updates reva dependency version + sums.
.gitignore Ignores .agents/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

},
"protected-personal": map[string]interface{}{
"mount_point": "/protected-users",
"path_template": "/protected-users/{{.Space.Name}}",
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new mount config for "protected-personal" uses path_template: /protected-users/{{.Space.Name}}. Since protected personal spaces are created with a fixed name ("Protected Personal"), this will cause path collisions across users and won’t match the default reva template (which uses the owner id). Use the owner id in the path template (similar to the existing personal space config).

Suggested change
"path_template": "/protected-users/{{.Space.Name}}",
"path_template": "/protected-users/{{.Space.Owner.Id.OpaqueId}}",

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a "special" personal folder, so it makes sense to have the same formatting as with personal folders

},
"protected-personal": map[string]interface{}{
"mount_point": "/protected-users",
"path_template": "/protected-users/{{.Space.Name}}",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a "special" personal folder, so it makes sense to have the same formatting as with personal folders

logger := g.logger.SubloggerWithRequestID(r.Context())
logger.Error().Str("path", r.URL.Path).Msg("MFA required but not satisfied")
mfa.SetRequiredStatus(w)
if !g.validateMFA(r, w) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure about this. There are a couple of thing to notice:

  • Most of the responses will be written in the public function. In this particular case, there will be information that will be written in a private function. This might be confusing in the long run because you might not expect the function to write an HTTP response.
  • Reusing the function might be limited to this particular use case. Basically, you'll be forced to write a wrong response if MFA fails to validate. You won't be able to add more information to the log or change the log message. In addition, the log will likely point to the private function, so knowing what function have failed will be more difficult (you'll likely need to check and map the request instead of getting the information from the log directly).

I'm not against the change, but I'm not sure if it outweighs the disadvantages.


// getDrives implements the Service interface.
func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersion) ([]*libregraph.Drive, error) {
func (g Graph) getDrives(w http.ResponseWriter, r *http.Request, unrestricted bool, apiVersion APIVersion) ([]*libregraph.Drive, error) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fond of passing the ResponseWriter around. Ideally, there should be only one place allowed for writing the HTTP response, and that should be the public service's method.
This might get problematic, not only because several places will write a HTTP response, but also because the message will be the same in all of them. Figuring out who wrote the HTTP response will be annoying.

return res, nil
}

// Filter out protected spaces if MFA is not enabled
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we request first the non-protected folders and then the protected ones if we have MFA? Or check the MFA first and request only the non-protected folders if we don't have MFA?
I'm not sure how it's implemented in reva, but at least there should be less network traffic (less data we need to transfer) and no need to filter results, so it should perform faster.

type DriveTypeValidator struct{}

// Validate checks if the driveType filter contains protected types.
func (v DriveTypeValidator) Validate(query *godata.GoDataQuery) error {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be needed... Any chance to move the drive check into reva?
Reva should be able to check somehow whether request has MFA or not, and then decide if the send the protected spaces or not.
I mean, if you are under MFA, why can't you list or show protected spaces?

@jvillafanez
Copy link
Copy Markdown
Member

We might need to discuss about the protected personal space. It seems this PR implements it as a new type of space... in my head I thought it would be implemented as part of the home creation: when you create the home, you create both the personal and the protected personal spaces.

I haven't check in depth, so there might be problems with that approach, mostly incompatibilities with the storage providers, but on the other hand it could be fine if some providers don't implement the feature and don't create the protected personal space as part of the home creation.

@2403905
Copy link
Copy Markdown
Contributor Author

2403905 commented Mar 9, 2026

I have tried another approach. Maybe we can discuss it to. The idea was to use the ocis server + sidecarocis graph server and ocis storage-users server for vault. The vault graph uses the route prefix GRAPH_HTTP_ROOT=/vault/graph and will be pointed to the vault storage-user "com.owncloud.api.storage-users-vault"

+++ b/services/gateway/pkg/revaconfig/config.go
@@ -11,6 +11,10 @@ import (
        "github.com/owncloud/reva/v2/pkg/utils"
 )
 
+const (
+       ProtectedStID = "bbbbbbbb-16f5-444e-8a6a-a28db41bbbbb"
+)
+
 // GatewayConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service.
 func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]interface{} {
        localEndpoint := pkgconfig.LocalEndpoint(cfg.GRPC.Protocol, cfg.GRPC.Addr)
@@ -162,6 +166,19 @@ func spacesProviders(cfg *config.Config, logger log.Logger) map[string]map[strin
                                },
                        },
                },
+               "com.owncloud.api.storage-users-vault": {
+                       "providerid": ProtectedStID,
+                       "spaces": map[string]interface{}{
+                               "personal": map[string]interface{}{
+                                       "mount_point":   "/vault/users",
+                                       "path_template": "/vault/users/{{.Space.Owner.Id.OpaqueId}}",
+                               },
+                               "project": map[string]interface{}{
+                                       "mount_point":   "/vault/projects",
+                                       "path_template": "/vault/projects/{{.Space.Name}}",
+                               },
+                       },
+               },
                cfg.StorageSharesEndpoint: {
                        "providerid": utils.ShareStorageProviderID,
                        "spaces": map[string]interface{}{
IDM_ADMIN_PASSWORD=admin DEMO_USERS=true PROXY_ENABLE_BASIC_AUTH=true ./ocis/bin/ocis server

MICRO_SERVER_NAME=com.owncloud.web.graph-vault \
OCIS_LOG_LEVEL=debug \
GRAPH_HTTP_ADDR=127.0.0.1:9125 \
GRAPH_DEBUG_ADDR=127.0.0.1:9126 \
GRAPH_HTTP_ROOT=/vault/graph \
GRAPH_SERVICE_NAME=graph-vault \
GRAPH_SPACES_STORAGE_USERS_ADDRESS=com.owncloud.api.storage-users-vault \
./ocis/bin/ocis graph server


OCIS_LOG_LEVEL=debug \
STORAGE_USERS_SERVICE_NAME=storage-users-vault \
STORAGE_USERS_GRPC_ADDR=0.0.0.0:9167 \
STORAGE_USERS_HTTP_ADDR=0.0.0.0:9168 \
STORAGE_USERS_DEBUG_ADDR=0.0.0.0:9169 \
STORAGE_USERS_OCIS_ROOT=${HOME}/.ocis/vault/storage/users \
./ocis/bin/ocis storage-users server

@2403905
Copy link
Copy Markdown
Contributor Author

2403905 commented Mar 17, 2026

Closed in favor #12108

@2403905 2403905 closed this Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants