Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ require (
github.com/onsi/gomega v1.42.1
github.com/open-policy-agent/opa v1.19.1
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901115715-6c4a98340429
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260902170011-45af3945a067
github.com/opencloud-eu/reva/v2 v2.49.0
github.com/opensearch-project/opensearch-go/v4 v4.7.3
github.com/orcaman/concurrent-map v1.0.0
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -940,10 +940,8 @@ github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-202505121527
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY=
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+lP5lUUIzjRGDg93WrQfZJZCaV1ZP3KeyXi8bzY=
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89/go.mod h1:vigJkNss1N2QEceCuNw/ullDehncuJNFB6mEnzfq9UI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901070651-08a5330ce57d h1:3zbb31655ZvqwLMWMIiUAhYer6YcFnv/oguujnX7aOU=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901070651-08a5330ce57d/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901115715-6c4a98340429 h1:i/C5/1I0eRkjOcdtqQboyPvV0bPyAOahe/wWHGupKVY=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901115715-6c4a98340429/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260902170011-45af3945a067 h1:UkNMKauyJAzY6RE6mmthz9bQZLYkbvBuApm7ZDCparE=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260902170011-45af3945a067/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU=
github.com/opencloud-eu/reva/v2 v2.49.0 h1:AwECMDDth3NUaihZRf9bI9HNpWutvaRijOCvQyQfTT4=
github.com/opencloud-eu/reva/v2 v2.49.0/go.mod h1:Frg+UWnVcSy+412UB3l2LcD0KY8ZNu1samimwkNywbg=
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 h1:l2oB/RctH+t8r7QBj5p8thfEHCM/jF35aAY3WQ3hADI=
Expand Down
36 changes: 36 additions & 0 deletions services/graph/pkg/service/v0/driveitems.go
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,8 @@ func cs3ResourceToDriveItem(logger *log.Logger, publicBaseURL *url.URL, res *sto
}
}

driveItem.LockInfo = lockToFacet(res.GetLock())

if utils.IsProcessing(res) {
// queuedDateTime stays absent, we do not track when postprocessing started
driveItem.PendingOperations = &libregraph.PendingOperations{
Expand All @@ -564,6 +566,40 @@ func cs3ResourceToDriveItem(logger *log.Logger, publicBaseURL *url.URL, res *sto
return driveItem, nil
}

// lockToFacet maps a CS3 lock; WebDAV renders the same state as d:lockdiscovery.
// Only exclusive locks are reported, that is all OpenCloud issues.
func lockToFacet(lock *storageprovider.Lock) *libregraph.LockInfo {
switch lock.GetType() {
case storageprovider.LockType_LOCK_TYPE_EXCL, storageprovider.LockType_LOCK_TYPE_WRITE:
default:
return nil
}

info := libregraph.NewLockInfo()
info.SetLockType("exclusive")

if appName := lock.GetAppName(); appName != "" {
info.SetLibreGraphAppName(appName)
}

if user := lock.GetUser(); user != nil {
info.SetOwners([]libregraph.Identity{{
Id: libregraph.PtrString(user.GetOpaqueId()),
DisplayName: utils.ReadPlainFromOpaque(lock.GetOpaque(), "lockownername"),
}})
}

if lockTime, err := time.Parse(time.RFC3339, utils.ReadPlainFromOpaque(lock.GetOpaque(), "locktime")); err == nil {
info.SetCreatedDateTime(lockTime.UTC())
}

if expiration := lock.GetExpiration(); expiration != nil {
info.SetExpirationDateTime(time.Unix(int64(expiration.GetSeconds()), 0).UTC())
}

return info
}

// addShareTypes reads user and group shares off the grants, links from the share
// manager. Links are not stored as grants, so they need one filter per item,
// which is why the whole annotation is only built when it is selected.
Expand Down
55 changes: 55 additions & 0 deletions services/graph/pkg/service/v0/driveitems_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/go-chi/chi/v5"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -535,6 +536,60 @@ var _ = Describe("Driveitems", func() {
Expect(res.Value[0].LibreGraphShareTypes).To(ConsistOf("user"))
})

It("returns the lock info of a locked item", func() {
// a lock time with a non-UTC offset, the way reva writes it
lockTime := time.Now().Truncate(time.Second).In(time.FixedZone("CEST", 2*60*60))
gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
Status: status.NewOK(ctx),
Infos: []*provider.ResourceInfo{
{
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
Id: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"},
Etag: "etag",
Mtime: utils.TimeToTS(mtime),
Lock: &provider.Lock{
Type: provider.LockType_LOCK_TYPE_EXCL,
AppName: "Collabora",
User: &userpb.UserId{OpaqueId: "user-id"},
Expiration: &typesv1beta1.Timestamp{Seconds: uint64(lockTime.Add(time.Hour).Unix())},
Opaque: utils.AppendPlainToOpaque(
utils.AppendPlainToOpaque(nil, "lockownername", "Alice Hansen"),
"locktime", lockTime.Format(time.RFC3339)),
},
},
},
}, nil)

res := assertItemsList(1)
lock := res.Value[0].LockInfo
Expect(lock).ToNot(BeNil())
Expect(lock.GetLockType()).To(Equal("exclusive"))
Expect(lock.GetLibreGraphAppName()).To(Equal("Collabora"))
Expect(lock.GetCreatedDateTime()).To(BeTemporally("==", lockTime))
Expect(lock.GetCreatedDateTime().Location()).To(Equal(time.UTC))
Expect(lock.GetExpirationDateTime().Location()).To(Equal(time.UTC))
Expect(lock.GetExpirationDateTime()).To(BeTemporally("==", lockTime.Add(time.Hour)))
Expect(lock.GetOwners()).To(HaveLen(1))
Expect(lock.GetOwners()[0].GetId()).To(Equal("user-id"))
Expect(lock.GetOwners()[0].GetDisplayName()).To(Equal("Alice Hansen"))
})

It("omits the lock info for an unlocked item", func() {
gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
Status: status.NewOK(ctx),
Infos: []*provider.ResourceInfo{
{
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
Id: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"},
Etag: "etag",
Mtime: utils.TimeToTS(mtime),
},
},
}, nil)

Expect(assertItemsList(1).Value[0].LockInfo).To(BeNil())
})

It("reports a pending content update while the item is being processed", func() {
gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
Status: status.NewOK(ctx),
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading