diff --git a/changelog/unreleased/group-sharing.md b/changelog/unreleased/group-sharing.md new file mode 100644 index 0000000000..a976fb924d --- /dev/null +++ b/changelog/unreleased/group-sharing.md @@ -0,0 +1,3 @@ +Enhancement: Add functionality to share resources with groups + +https://github.com/cs3org/reva/pull/1453 diff --git a/cmd/reva/ocm-share-create.go b/cmd/reva/ocm-share-create.go index a7783ccfab..b073eeedbc 100644 --- a/cmd/reva/ocm-share-create.go +++ b/cmd/reva/ocm-share-create.go @@ -110,10 +110,12 @@ func ocmShareCreateCommand() *command { Permissions: perm, Grantee: &provider.Grantee{ Type: gt, - Id: &userpb.UserId{ + // For now, we only support user shares. + // TODO (ishank011): To be updated once this is decided. + Id: &provider.Grantee_UserId{UserId: &userpb.UserId{ Idp: *idp, OpaqueId: *grantee, - }, + }}, }, } @@ -153,7 +155,9 @@ func ocmShareCreateCommand() *command { s := shareRes.Share t.AppendRows([]table.Row{ - {s.Id.OpaqueId, s.Owner.Idp, s.Owner.OpaqueId, s.ResourceId.String(), s.Permissions.String(), s.Grantee.Type.String(), s.Grantee.Id.Idp, s.Grantee.Id.OpaqueId, time.Unix(int64(s.Ctime.Seconds), 0), time.Unix(int64(s.Mtime.Seconds), 0)}, + {s.Id.OpaqueId, s.Owner.Idp, s.Owner.OpaqueId, s.ResourceId.String(), s.Permissions.String(), + s.Grantee.Type.String(), s.Grantee.GetUserId().Idp, s.Grantee.GetUserId().OpaqueId, + time.Unix(int64(s.Ctime.Seconds), 0), time.Unix(int64(s.Mtime.Seconds), 0)}, }) t.Render() diff --git a/cmd/reva/ocm-share-list-received.go b/cmd/reva/ocm-share-list-received.go index 9128e5b734..83a1122086 100644 --- a/cmd/reva/ocm-share-list-received.go +++ b/cmd/reva/ocm-share-list-received.go @@ -54,10 +54,14 @@ func ocmShareListReceivedCommand() *command { if len(w) == 0 { t := table.NewWriter() t.SetOutputMirror(os.Stdout) - t.AppendHeader(table.Row{"#", "Owner.Idp", "Owner.OpaqueId", "ResourceId", "Permissions", "Type", "Grantee.Idp", "Grantee.OpaqueId", "Created", "Updated", "State"}) + t.AppendHeader(table.Row{"#", "Owner.Idp", "Owner.OpaqueId", "ResourceId", "Permissions", "Type", + "Grantee.Idp", "Grantee.OpaqueId", "Created", "Updated", "State"}) for _, s := range shareRes.Shares { t.AppendRows([]table.Row{ - {s.Share.Id.OpaqueId, s.Share.Owner.Idp, s.Share.Owner.OpaqueId, s.Share.ResourceId.String(), s.Share.Permissions.String(), s.Share.Grantee.Type.String(), s.Share.Grantee.Id.Idp, s.Share.Grantee.Id.OpaqueId, time.Unix(int64(s.Share.Ctime.Seconds), 0), time.Unix(int64(s.Share.Mtime.Seconds), 0), s.State.String()}, + {s.Share.Id.OpaqueId, s.Share.Owner.Idp, s.Share.Owner.OpaqueId, s.Share.ResourceId.String(), + s.Share.Permissions.String(), s.Share.Grantee.Type.String(), s.Share.Grantee.GetUserId().Idp, + s.Share.Grantee.GetUserId().OpaqueId, time.Unix(int64(s.Share.Ctime.Seconds), 0), + time.Unix(int64(s.Share.Mtime.Seconds), 0), s.State.String()}, }) } t.Render() diff --git a/cmd/reva/ocm-share-list.go b/cmd/reva/ocm-share-list.go index 7f878a2631..9229b87e60 100644 --- a/cmd/reva/ocm-share-list.go +++ b/cmd/reva/ocm-share-list.go @@ -82,11 +82,14 @@ func ocmShareListCommand() *command { if len(w) == 0 { t := table.NewWriter() t.SetOutputMirror(os.Stdout) - t.AppendHeader(table.Row{"#", "Owner.Idp", "Owner.OpaqueId", "ResourceId", "Permissions", "Type", "Grantee.Idp", "Grantee.OpaqueId", "Created", "Updated"}) + t.AppendHeader(table.Row{"#", "Owner.Idp", "Owner.OpaqueId", "ResourceId", "Permissions", "Type", + "Grantee.Idp", "Grantee.OpaqueId", "Created", "Updated"}) for _, s := range shareRes.Shares { t.AppendRows([]table.Row{ - {s.Id.OpaqueId, s.Owner.Idp, s.Owner.OpaqueId, s.ResourceId.String(), s.Permissions.String(), s.Grantee.Type.String(), s.Grantee.Id.Idp, s.Grantee.Id.OpaqueId, time.Unix(int64(s.Ctime.Seconds), 0), time.Unix(int64(s.Mtime.Seconds), 0)}, + {s.Id.OpaqueId, s.Owner.Idp, s.Owner.OpaqueId, s.ResourceId.String(), s.Permissions.String(), + s.Grantee.Type.String(), s.Grantee.GetUserId().Idp, s.Grantee.GetUserId().OpaqueId, + time.Unix(int64(s.Ctime.Seconds), 0), time.Unix(int64(s.Mtime.Seconds), 0)}, }) } t.Render() diff --git a/cmd/reva/share-create.go b/cmd/reva/share-create.go index 5da1d2c37c..77194f07ab 100644 --- a/cmd/reva/share-create.go +++ b/cmd/reva/share-create.go @@ -23,6 +23,7 @@ import ( "os" "time" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -89,12 +90,23 @@ func shareCreateCommand() *command { }, Grantee: &provider.Grantee{ Type: gt, - Id: &userpb.UserId{ - Idp: *idp, - OpaqueId: *grantee, - }, }, } + switch *grantType { + case "user": + grant.Grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{ + Idp: *idp, + OpaqueId: *grantee, + }} + case "group": + grant.Grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{ + Idp: *idp, + OpaqueId: *grantee, + }} + default: + return errors.New("Invalid grantee type argument: " + *grantType) + } + shareRequest := &collaboration.CreateShareRequest{ ResourceInfo: res.Info, Grant: grant, @@ -114,8 +126,16 @@ func shareCreateCommand() *command { t.AppendHeader(table.Row{"#", "Owner.Idp", "Owner.OpaqueId", "ResourceId", "Permissions", "Type", "Grantee.Idp", "Grantee.OpaqueId", "Created", "Updated"}) s := shareRes.Share + var idp, opaque string + if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { + idp, opaque = s.Grantee.GetUserId().Idp, s.Grantee.GetUserId().OpaqueId + } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + idp, opaque = s.Grantee.GetGroupId().Idp, s.Grantee.GetGroupId().OpaqueId + } t.AppendRows([]table.Row{ - {s.Id.OpaqueId, s.Owner.Idp, s.Owner.OpaqueId, s.ResourceId.String(), s.Permissions.String(), s.Grantee.Type.String(), s.Grantee.Id.Idp, s.Grantee.Id.OpaqueId, time.Unix(int64(s.Ctime.Seconds), 0), time.Unix(int64(s.Mtime.Seconds), 0)}, + {s.Id.OpaqueId, s.Owner.Idp, s.Owner.OpaqueId, s.ResourceId.String(), s.Permissions.String(), + s.Grantee.Type.String(), idp, opaque, + time.Unix(int64(s.Ctime.Seconds), 0), time.Unix(int64(s.Mtime.Seconds), 0)}, }) t.Render() diff --git a/cmd/reva/share-list-received.go b/cmd/reva/share-list-received.go index 450ab979ee..97c7d82b19 100644 --- a/cmd/reva/share-list-received.go +++ b/cmd/reva/share-list-received.go @@ -26,6 +26,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/jedib0t/go-pretty/table" ) @@ -54,10 +55,20 @@ func shareListReceivedCommand() *command { if len(w) == 0 { t := table.NewWriter() t.SetOutputMirror(os.Stdout) - t.AppendHeader(table.Row{"#", "Owner.Idp", "Owner.OpaqueId", "ResourceId", "Permissions", "Type", "Grantee.Idp", "Grantee.OpaqueId", "Created", "Updated", "State"}) + t.AppendHeader(table.Row{"#", "Owner.Idp", "Owner.OpaqueId", "ResourceId", "Permissions", "Type", + "Grantee.Idp", "Grantee.OpaqueId", "Created", "Updated", "State"}) for _, s := range shareRes.Shares { + var idp, opaque string + if s.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { + idp, opaque = s.Share.Grantee.GetUserId().Idp, s.Share.Grantee.GetUserId().OpaqueId + } else if s.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + idp, opaque = s.Share.Grantee.GetGroupId().Idp, s.Share.Grantee.GetGroupId().OpaqueId + } t.AppendRows([]table.Row{ - {s.Share.Id.OpaqueId, s.Share.Owner.Idp, s.Share.Owner.OpaqueId, s.Share.ResourceId.String(), s.Share.Permissions.String(), s.Share.Grantee.Type.String(), s.Share.Grantee.Id.Idp, s.Share.Grantee.Id.OpaqueId, time.Unix(int64(s.Share.Ctime.Seconds), 0), time.Unix(int64(s.Share.Mtime.Seconds), 0), s.State.String()}, + {s.Share.Id.OpaqueId, s.Share.Owner.Idp, s.Share.Owner.OpaqueId, s.Share.ResourceId.String(), + s.Share.Permissions.String(), s.Share.Grantee.Type.String(), idp, + opaque, time.Unix(int64(s.Share.Ctime.Seconds), 0), + time.Unix(int64(s.Share.Mtime.Seconds), 0), s.State.String()}, }) } t.Render() diff --git a/cmd/reva/share-list.go b/cmd/reva/share-list.go index eedf4e74e6..73b005d0cd 100644 --- a/cmd/reva/share-list.go +++ b/cmd/reva/share-list.go @@ -82,11 +82,20 @@ func shareListCommand() *command { if len(w) == 0 { t := table.NewWriter() t.SetOutputMirror(os.Stdout) - t.AppendHeader(table.Row{"#", "Owner.Idp", "Owner.OpaqueId", "ResourceId", "Permissions", "Type", "Grantee.Idp", "Grantee.OpaqueId", "Created", "Updated"}) + t.AppendHeader(table.Row{"#", "Owner.Idp", "Owner.OpaqueId", "ResourceId", "Permissions", "Type", + "Grantee.Idp", "Grantee.OpaqueId", "Created", "Updated"}) for _, s := range shareRes.Shares { + var idp, opaque string + if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { + idp, opaque = s.Grantee.GetUserId().Idp, s.Grantee.GetUserId().OpaqueId + } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + idp, opaque = s.Grantee.GetGroupId().Idp, s.Grantee.GetGroupId().OpaqueId + } t.AppendRows([]table.Row{ - {s.Id.OpaqueId, s.Owner.Idp, s.Owner.OpaqueId, s.ResourceId.String(), s.Permissions.String(), s.Grantee.Type.String(), s.Grantee.Id.Idp, s.Grantee.Id.OpaqueId, time.Unix(int64(s.Ctime.Seconds), 0), time.Unix(int64(s.Mtime.Seconds), 0)}, + {s.Id.OpaqueId, s.Owner.Idp, s.Owner.OpaqueId, s.ResourceId.String(), s.Permissions.String(), + s.Grantee.Type.String(), idp, opaque, + time.Unix(int64(s.Ctime.Seconds), 0), time.Unix(int64(s.Mtime.Seconds), 0)}, }) } t.Render() diff --git a/examples/oc-phoenix/groups.demo.json b/examples/oc-phoenix/groups.demo.json new file mode 100644 index 0000000000..f78da191bc --- /dev/null +++ b/examples/oc-phoenix/groups.demo.json @@ -0,0 +1,140 @@ +[ + { + "id": { + "opaque_id": "sailing-lovers", + "idp": "localhost:20080" + }, + "group_name": "sailing-lovers", + "mail": "sailing-lovers@example.org", + "display_name": "Sailing Lovers", + "gid_number": 123, + "members": [ + { + "id": { + "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "violin-haters", + "idp": "localhost:20080" + }, + "group_name": "violin-haters", + "mail": "violin-haters@example.org", + "display_name": "Violin Haters", + "gid_number": 456, + "members": [ + { + "id": { + "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "radium-lovers", + "idp": "localhost:20080" + }, + "group_name": "radium-lovers", + "mail": "radium-lovers@example.org", + "display_name": "Radium Lovers", + "gid_number": 789, + "members": [ + { + "id": { + "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "polonium-lovers", + "idp": "localhost:20080" + }, + "group_name": "polonium-lovers", + "mail": "polonium-lovers@example.org", + "display_name": "Polonium Lovers", + "gid_number": 987, + "members": [ + { + "id": { + "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "quantum-lovers", + "idp": "localhost:20080" + }, + "group_name": "quantum-lovers", + "mail": "quantum-lovers@example.org", + "display_name": "Quantum Lovers", + "gid_number": 654, + "members": [ + { + "id": { + "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "philosophy-haters", + "idp": "localhost:20080" + }, + "group_name": "philosophy-haters", + "mail": "philosophy-haters@example.org", + "display_name": "Philosophy Haters", + "gid_number": 321, + "members": [ + { + "id": { + "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "physics-lovers", + "idp": "localhost:20080" + }, + "group_name": "physics-lovers", + "mail": "physics-lovers@example.org", + "display_name": "Physics Lovers", + "gid_number": 101, + "members": [ + { + "id": { + "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", + "idp": "localhost:20080" + } + }, + { + "id": { + "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + "idp": "localhost:20080" + } + }, + { + "id": { + "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", + "idp": "localhost:20080" + } + } + ] + } +] diff --git a/examples/standalone/groups.demo.json b/examples/standalone/groups.demo.json new file mode 100644 index 0000000000..f78da191bc --- /dev/null +++ b/examples/standalone/groups.demo.json @@ -0,0 +1,140 @@ +[ + { + "id": { + "opaque_id": "sailing-lovers", + "idp": "localhost:20080" + }, + "group_name": "sailing-lovers", + "mail": "sailing-lovers@example.org", + "display_name": "Sailing Lovers", + "gid_number": 123, + "members": [ + { + "id": { + "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "violin-haters", + "idp": "localhost:20080" + }, + "group_name": "violin-haters", + "mail": "violin-haters@example.org", + "display_name": "Violin Haters", + "gid_number": 456, + "members": [ + { + "id": { + "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "radium-lovers", + "idp": "localhost:20080" + }, + "group_name": "radium-lovers", + "mail": "radium-lovers@example.org", + "display_name": "Radium Lovers", + "gid_number": 789, + "members": [ + { + "id": { + "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "polonium-lovers", + "idp": "localhost:20080" + }, + "group_name": "polonium-lovers", + "mail": "polonium-lovers@example.org", + "display_name": "Polonium Lovers", + "gid_number": 987, + "members": [ + { + "id": { + "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "quantum-lovers", + "idp": "localhost:20080" + }, + "group_name": "quantum-lovers", + "mail": "quantum-lovers@example.org", + "display_name": "Quantum Lovers", + "gid_number": 654, + "members": [ + { + "id": { + "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "philosophy-haters", + "idp": "localhost:20080" + }, + "group_name": "philosophy-haters", + "mail": "philosophy-haters@example.org", + "display_name": "Philosophy Haters", + "gid_number": 321, + "members": [ + { + "id": { + "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "physics-lovers", + "idp": "localhost:20080" + }, + "group_name": "physics-lovers", + "mail": "physics-lovers@example.org", + "display_name": "Physics Lovers", + "gid_number": 101, + "members": [ + { + "id": { + "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", + "idp": "localhost:20080" + } + }, + { + "id": { + "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + "idp": "localhost:20080" + } + }, + { + "id": { + "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", + "idp": "localhost:20080" + } + } + ] + } +] diff --git a/examples/standalone/standalone.toml b/examples/standalone/standalone.toml index 973a48be95..fe293dbb01 100644 --- a/examples/standalone/standalone.toml +++ b/examples/standalone/standalone.toml @@ -5,6 +5,7 @@ [grpc.services.authprovider] [grpc.services.authregistry] [grpc.services.userprovider] +[grpc.services.groupprovider] [grpc.services.usershareprovider] [grpc.services.publicshareprovider] [grpc.services.ocmcore] @@ -17,3 +18,4 @@ [http.services.prometheus] [http.services.ocmd] [http.services.ocdav] +[http.services.ocs] diff --git a/examples/storage-references/gateway.toml b/examples/storage-references/gateway.toml index 35d7b429cf..7a5359a2db 100644 --- a/examples/storage-references/gateway.toml +++ b/examples/storage-references/gateway.toml @@ -16,6 +16,7 @@ home_provider = "/home" [grpc.services.authregistry] [grpc.services.userprovider] [grpc.services.usershareprovider] +[grpc.services.groupprovider] [grpc.services.publicshareprovider] [grpc.services.ocmcore] [grpc.services.ocmshareprovider] @@ -26,3 +27,4 @@ home_provider = "/home" [http.services.prometheus] [http.services.ocmd] [http.services.ocdav] +[http.services.ocs] diff --git a/examples/storage-references/groups.demo.json b/examples/storage-references/groups.demo.json new file mode 100644 index 0000000000..f78da191bc --- /dev/null +++ b/examples/storage-references/groups.demo.json @@ -0,0 +1,140 @@ +[ + { + "id": { + "opaque_id": "sailing-lovers", + "idp": "localhost:20080" + }, + "group_name": "sailing-lovers", + "mail": "sailing-lovers@example.org", + "display_name": "Sailing Lovers", + "gid_number": 123, + "members": [ + { + "id": { + "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "violin-haters", + "idp": "localhost:20080" + }, + "group_name": "violin-haters", + "mail": "violin-haters@example.org", + "display_name": "Violin Haters", + "gid_number": 456, + "members": [ + { + "id": { + "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "radium-lovers", + "idp": "localhost:20080" + }, + "group_name": "radium-lovers", + "mail": "radium-lovers@example.org", + "display_name": "Radium Lovers", + "gid_number": 789, + "members": [ + { + "id": { + "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "polonium-lovers", + "idp": "localhost:20080" + }, + "group_name": "polonium-lovers", + "mail": "polonium-lovers@example.org", + "display_name": "Polonium Lovers", + "gid_number": 987, + "members": [ + { + "id": { + "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "quantum-lovers", + "idp": "localhost:20080" + }, + "group_name": "quantum-lovers", + "mail": "quantum-lovers@example.org", + "display_name": "Quantum Lovers", + "gid_number": 654, + "members": [ + { + "id": { + "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "philosophy-haters", + "idp": "localhost:20080" + }, + "group_name": "philosophy-haters", + "mail": "philosophy-haters@example.org", + "display_name": "Philosophy Haters", + "gid_number": 321, + "members": [ + { + "id": { + "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", + "idp": "localhost:20080" + } + } + ] + }, + { + "id": { + "opaque_id": "physics-lovers", + "idp": "localhost:20080" + }, + "group_name": "physics-lovers", + "mail": "physics-lovers@example.org", + "display_name": "Physics Lovers", + "gid_number": 101, + "members": [ + { + "id": { + "opaque_id": "4c510ada-c86b-4815-8820-42cdf82c3d51", + "idp": "localhost:20080" + } + }, + { + "id": { + "opaque_id": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", + "idp": "localhost:20080" + } + }, + { + "id": { + "opaque_id": "932b4540-8d16-481e-8ef4-588e4b6b151c", + "idp": "localhost:20080" + } + } + ] + } +] diff --git a/go.mod b/go.mod index e9e4ecaada..efae748133 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/cheggaaa/pb v1.0.29 github.com/coreos/go-oidc v2.2.1+incompatible github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e - github.com/cs3org/go-cs3apis v0.0.0-20210104105209-0d3ecb3453dc + github.com/cs3org/go-cs3apis v0.0.0-20210209082852-35ace33082f5 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 github.com/go-ldap/ldap/v3 v3.2.4 diff --git a/go.sum b/go.sum index 59cb545fb3..cc1004bf15 100644 --- a/go.sum +++ b/go.sum @@ -130,6 +130,8 @@ github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJff github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= github.com/cs3org/go-cs3apis v0.0.0-20210104105209-0d3ecb3453dc h1:vHFqu+Gb/iOKYFy2KswpwIG3G6zRMudRn+rQ2bg3TPE= github.com/cs3org/go-cs3apis v0.0.0-20210104105209-0d3ecb3453dc/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20210209082852-35ace33082f5 h1:wy1oeyy6v9/65G97AkE5o4jC9J+sngPV9AZL5TbNsaY= +github.com/cs3org/go-cs3apis v0.0.0-20210209082852-35ace33082f5/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/internal/grpc/services/ocmcore/ocmcore.go b/internal/grpc/services/ocmcore/ocmcore.go index 116919505d..9d69fcd8b4 100644 --- a/internal/grpc/services/ocmcore/ocmcore.go +++ b/internal/grpc/services/ocmcore/ocmcore.go @@ -162,7 +162,9 @@ func (s *service) CreateOCMCoreShare(ctx context.Context, req *ocmcore.CreateOCM grant := &ocm.ShareGrant{ Grantee: &provider.Grantee{ Type: provider.GranteeType_GRANTEE_TYPE_USER, - Id: req.ShareWith, + // For now, we only support user shares. + // TODO (ishank011): To be updated once this is decided. + Id: &provider.Grantee_UserId{UserId: req.ShareWith}, }, Permissions: &ocm.SharePermissions{ Permissions: resourcePermissions, diff --git a/internal/grpc/services/usershareprovider/usershareprovider.go b/internal/grpc/services/usershareprovider/usershareprovider.go index 8a23667a69..58e9782c9d 100644 --- a/internal/grpc/services/usershareprovider/usershareprovider.go +++ b/internal/grpc/services/usershareprovider/usershareprovider.go @@ -22,7 +22,9 @@ import ( "context" "fmt" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" @@ -108,10 +110,10 @@ func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShareRequest) (*collaboration.CreateShareResponse, error) { u := user.ContextMustGetUser(ctx) - // TODO(labkode): validate input - if req.Grant.Grantee.Id.Idp == "" { + if req.Grant.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && req.Grant.Grantee.GetUserId().Idp == "" { // use logged in user Idp as default. - req.Grant.Grantee.Id.Idp = u.Id.Idp + g := &userpb.UserId{OpaqueId: req.Grant.Grantee.GetUserId().OpaqueId, Idp: u.Id.Idp} + req.Grant.Grantee.Id = &provider.Grantee_UserId{UserId: g} } share, err := s.sm.Share(ctx, req.ResourceInfo, req.Grant) if err != nil { diff --git a/internal/http/services/owncloud/ocs/conversions/main.go b/internal/http/services/owncloud/ocs/conversions/main.go index f99e120126..eff6129444 100644 --- a/internal/http/services/owncloud/ocs/conversions/main.go +++ b/internal/http/services/owncloud/ocs/conversions/main.go @@ -29,9 +29,11 @@ import ( "github.com/cs3org/reva/pkg/publicshare" "github.com/cs3org/reva/pkg/user" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" publicsharemgr "github.com/cs3org/reva/pkg/publicshare/manager/registry" usermgr "github.com/cs3org/reva/pkg/user/manager/registry" @@ -45,7 +47,7 @@ const ( ShareTypePublicLink ShareType = 3 // ShareTypeGroup represents a group share - // ShareTypeGroup ShareType = 1 + ShareTypeGroup ShareType = 1 // ShareTypeFederatedCloudShare represents a federated share ShareTypeFederatedCloudShare ShareType = 6 @@ -169,16 +171,23 @@ type MatchValueData struct { ShareWithAdditionalInfo string `json:"shareWithAdditionalInfo" xml:"shareWithAdditionalInfo"` } -// UserShare2ShareData converts a cs3api user share into shareData data model -func UserShare2ShareData(ctx context.Context, share *collaboration.Share) (*ShareData, error) { +// CS3Share2ShareData converts a cs3api user share into shareData data model +func CS3Share2ShareData(ctx context.Context, share *collaboration.Share) (*ShareData, error) { sd := &ShareData{ // share.permissions are mapped below // Displaynames are added later - ShareType: ShareTypeUser, UIDOwner: LocalUserIDToString(share.GetCreator()), UIDFileOwner: LocalUserIDToString(share.GetOwner()), - ShareWith: LocalUserIDToString(share.GetGrantee().GetId()), } + + if share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { + sd.ShareType = ShareTypeUser + sd.ShareWith = LocalUserIDToString(share.Grantee.GetUserId()) + } else if share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + sd.ShareType = ShareTypeGroup + sd.ShareWith = LocalGroupIDToString(share.Grantee.GetGroupId()) + } + if share.Id != nil { sd.ID = share.Id.OpaqueId } @@ -188,7 +197,6 @@ func UserShare2ShareData(ctx context.Context, share *collaboration.Share) (*Shar if share.Ctime != nil { sd.STime = share.Ctime.Seconds // TODO CS3 api birth time = btime } - // TODO check grantee type for user vs group return sd, nil } @@ -236,6 +244,14 @@ func LocalUserIDToString(userID *userpb.UserId) string { return userID.OpaqueId } +// LocalGroupIDToString transforms a cs3api group id into an ocs data model without domain name +func LocalGroupIDToString(groupID *grouppb.GroupId) string { + if groupID == nil || groupID.OpaqueId == "" { + return "" + } + return groupID.OpaqueId +} + // UserIDToString transforms a cs3api user id into an ocs data model // TODO This should be used instead of LocalUserIDToString bit it requires interpreting an @ on the client side // TODO An alternative would be to send the idp / iss as an additional attribute. might be less intrusive diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go index 66f0006e9d..5bb51e7b08 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/sharees/sharees.go @@ -21,6 +21,7 @@ package sharees import ( "net/http" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" @@ -68,24 +69,32 @@ func (h *Handler) findSharees(w http.ResponseWriter, r *http.Request) { return } - req := userpb.FindUsersRequest{ - Filter: term, - } - - res, err := gwc.FindUsers(r.Context(), &req) + usersRes, err := gwc.FindUsers(r.Context(), &userpb.FindUsersRequest{Filter: term}) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching users", err) return } + log.Debug().Int("count", len(usersRes.GetUsers())).Str("search", term).Msg("users found") - log.Debug().Int("count", len(res.GetUsers())).Str("search", term).Msg("users found") - - matches := make([]*conversions.MatchData, 0, len(res.GetUsers())) - - for _, user := range res.GetUsers() { + userMatches := make([]*conversions.MatchData, 0, len(usersRes.GetUsers())) + for _, user := range usersRes.GetUsers() { match := h.userAsMatch(user) log.Debug().Interface("user", user).Interface("match", match).Msg("mapped") - matches = append(matches, match) + userMatches = append(userMatches, match) + } + + groupsRes, err := gwc.FindGroups(r.Context(), &grouppb.FindGroupsRequest{Filter: term}) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching groups", err) + return + } + log.Debug().Int("count", len(groupsRes.GetGroups())).Str("search", term).Msg("groups found") + + groupMatches := make([]*conversions.MatchData, 0, len(groupsRes.GetGroups())) + for _, g := range groupsRes.GetGroups() { + match := h.groupAsMatch(g) + log.Debug().Interface("group", g).Interface("match", match).Msg("mapped") + groupMatches = append(groupMatches, match) } response.WriteOCSSuccess(w, r, &conversions.ShareeData{ @@ -94,8 +103,8 @@ func (h *Handler) findSharees(w http.ResponseWriter, r *http.Request) { Groups: []*conversions.MatchData{}, Remotes: []*conversions.MatchData{}, }, - Users: matches, - Groups: []*conversions.MatchData{}, + Users: userMatches, + Groups: groupMatches, Remotes: []*conversions.MatchData{}, }) } @@ -111,3 +120,15 @@ func (h *Handler) userAsMatch(u *userpb.User) *conversions.MatchData { }, } } + +func (h *Handler) groupAsMatch(g *grouppb.Group) *conversions.MatchData { + return &conversions.MatchData{ + Label: g.DisplayName, + Value: &conversions.MatchValueData{ + ShareType: int(conversions.ShareTypeGroup), + // api compatibility with oc10 + ShareWith: g.GroupName, + ShareWithAdditionalInfo: g.Mail, + }, + } +} diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/group.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/group.go new file mode 100644 index 0000000000..9e24024c7b --- /dev/null +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/group.go @@ -0,0 +1,109 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package shares + +import ( + "net/http" + + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" +) + +func (h *Handler) createGroupShare(w http.ResponseWriter, r *http.Request, statInfo *provider.ResourceInfo, role *conversions.Role, roleVal []byte) { + ctx := r.Context() + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) + return + } + + shareWith := r.FormValue("shareWith") + if shareWith == "" { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "missing shareWith", nil) + return + } + + groupRes, err := c.GetGroupByClaim(ctx, &grouppb.GetGroupByClaimRequest{ + Claim: "group_name", + Value: shareWith, + }) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching recipient", err) + return + } + if groupRes.Status.Code != rpc.Code_CODE_OK { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "group not found", err) + return + } + + createShareReq := &collaboration.CreateShareRequest{ + Opaque: &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "role": { + Decoder: "json", + Value: roleVal, + }, + }, + }, + ResourceInfo: statInfo, + Grant: &collaboration.ShareGrant{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{GroupId: groupRes.Group.GetId()}, + }, + Permissions: &collaboration.SharePermissions{ + Permissions: role.CS3ResourcePermissions(), + }, + }, + } + + createShareResponse, err := c.CreateShare(ctx, createShareReq) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc create share request", err) + return + } + if createShareResponse.Status.Code != rpc.Code_CODE_OK { + if createShareResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) + return + } + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc create share request failed", err) + return + } + s, err := conversions.CS3Share2ShareData(ctx, createShareResponse.Share) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) + return + } + err = h.addFileInfo(ctx, s, statInfo) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error adding fileinfo to share", err) + return + } + h.mapUserIds(ctx, c, s) + + response.WriteOCSSuccess(w, r, s) +} diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go index 35d8748c49..45eb94619c 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go @@ -28,7 +28,7 @@ import ( "github.com/pkg/errors" ) -func (h *Handler) acceptShare(w http.ResponseWriter, r *http.Request, shareID string) { +func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, shareID string, rejectShare bool) { ctx := r.Context() uClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) @@ -51,48 +51,17 @@ func (h *Handler) acceptShare(w http.ResponseWriter, r *http.Request, shareID st }, }, } - - shareRes, err := uClient.UpdateReceivedShare(ctx, shareRequest) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request (accept) failed", err) - return - } - - if shareRes.Status.Code != rpc.Code_CODE_OK { - if shareRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) - return - } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request (accept) failed", errors.Errorf("code: %d, message: %s", shareRes.Status.Code, shareRes.Status.Message)) - return - } -} -func (h *Handler) rejectShare(w http.ResponseWriter, r *http.Request, shareID string) { - ctx := r.Context() - uClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) - return - } - - shareRequest := &collaboration.UpdateReceivedShareRequest{ - Ref: &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: shareID, - }, - }, - }, - Field: &collaboration.UpdateReceivedShareRequest_UpdateField{ + if rejectShare { + shareRequest.Field = &collaboration.UpdateReceivedShareRequest_UpdateField{ Field: &collaboration.UpdateReceivedShareRequest_UpdateField_State{ State: collaboration.ShareState_SHARE_STATE_REJECTED, }, - }, + } } shareRes, err := uClient.UpdateReceivedShare(ctx, shareRequest) if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request (reject) failed", err) + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request failed", err) return } @@ -101,7 +70,7 @@ func (h *Handler) rejectShare(w http.ResponseWriter, r *http.Request, shareID st response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) return } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request (reject) failed", errors.Errorf("code: %d, message: %s", shareRes.Status.Code, shareRes.Status.Message)) + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request failed", errors.Errorf("code: %d, message: %s", shareRes.Status.Code, shareRes.Status.Message)) return } } diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go index 3a889d8bd6..ce685315b7 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go @@ -35,7 +35,7 @@ import ( "github.com/cs3org/reva/pkg/rgrpc/todo/pool" ) -func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Request, statInfo *provider.ResourceInfo) { +func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Request, statInfo *provider.ResourceInfo, role *conversions.Role, roleVal []byte) { ctx := r.Context() c, err := pool.GetGatewayServiceClient(h.gatewayAddr) @@ -70,34 +70,6 @@ func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Reque return } - var role *conversions.Role - - pval := r.FormValue("permissions") - if pval == "" { - // by default only allow read permissions / assign viewer role - role = conversions.NewViewerRole() - } else { - pint, err := strconv.Atoi(pval) - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "permissions must be an integer", nil) - return - } - permissions, err := conversions.NewPermissions(pint) - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, err.Error(), nil) - return - } - role = conversions.RoleFromOCSPermissions(permissions) - } - - if statInfo != nil && statInfo.Type == provider.ResourceType_RESOURCE_TYPE_FILE { - // Single file shares should never have delete or create permissions - permissions := role.OCSPermissions() - permissions &^= conversions.PermissionCreate - permissions &^= conversions.PermissionDelete - role = conversions.RoleFromOCSPermissions(permissions) - } - createShareReq := &ocm.CreateOCMShareRequest{ Opaque: &types.Opaque{ Map: map[string]*types.OpaqueEntry{ @@ -121,7 +93,7 @@ func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Reque Grant: &ocm.ShareGrant{ Grantee: &provider.Grantee{ Type: provider.GranteeType_GRANTEE_TYPE_USER, - Id: remoteUserRes.RemoteUser.GetId(), + Id: &provider.Grantee_UserId{UserId: remoteUserRes.RemoteUser.GetId()}, }, Permissions: &ocm.SharePermissions{ Permissions: role.CS3ResourcePermissions(), diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index f02eef6b08..f4ddf401af 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -21,14 +21,17 @@ package shares import ( "context" "encoding/base64" + "encoding/json" "fmt" "mime" "net/http" "path" "strconv" "strings" + "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -36,6 +39,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/rs/zerolog/log" + "github.com/ReneKroon/ttlcache/v2" "github.com/cs3org/reva/internal/http/services/owncloud/ocdav" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" @@ -43,7 +47,6 @@ import ( "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp/router" - "github.com/cs3org/reva/pkg/ttlmap" "github.com/pkg/errors" ) @@ -52,7 +55,7 @@ type Handler struct { gatewayAddr string publicURL string sharePrefix string - userIdentifierCache *ttlmap.TTLMap + userIdentifierCache *ttlcache.Cache } // we only cache the minimal set of data instead of the full user metadata @@ -66,8 +69,11 @@ type userIdentifiers struct { func (h *Handler) Init(c *config.Config) error { h.gatewayAddr = c.GatewaySvc h.publicURL = c.Config.Host - h.userIdentifierCache = ttlmap.New(1000, 60) h.sharePrefix = c.SharePrefix + + h.userIdentifierCache = ttlcache.NewCache() + _ = h.userIdentifierCache.SetTTL(60 * time.Second) + return nil } @@ -95,6 +101,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { default: response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Only GET, POST and PUT are allowed", nil) } + case "pending": var shareID string shareID, r.URL.Path = router.ShiftPath(r.URL.Path) @@ -103,12 +110,13 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.Method { case "POST": - h.acceptShare(w, r, shareID) + h.updateReceivedShare(w, r, shareID, false) case "DELETE": - h.rejectShare(w, r, shareID) + h.updateReceivedShare(w, r, shareID, true) default: response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Only POST and DELETE are allowed", nil) } + case "remote_shares": var shareID string shareID, r.URL.Path = router.ShiftPath(r.URL.Path) @@ -125,6 +133,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { default: response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Only GET method is allowed", nil) } + default: switch r.Method { case "GET": @@ -143,7 +152,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.removePublicShare(w, r, shareID) return } - h.removeUserShare(w, r, head) default: response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Only GET, POST and PUT are allowed", nil) @@ -207,72 +215,87 @@ func (h *Handler) createShare(w http.ResponseWriter, r *http.Request) { switch shareType { case int(conversions.ShareTypeUser): // user collaborations default to coowner - if h.validatePermissions(w, r, statRes.Info, conversions.NewCoownerRole().OCSPermissions()) { - h.createUserShare(w, r, statRes.Info) + if role, val, err := h.extractPermissions(w, r, statRes.Info, conversions.NewCoownerRole()); err == nil { + h.createUserShare(w, r, statRes.Info, role, val) + } + case int(conversions.ShareTypeGroup): + // group collaborations default to coowner + if role, val, err := h.extractPermissions(w, r, statRes.Info, conversions.NewCoownerRole()); err == nil { + h.createGroupShare(w, r, statRes.Info, role, val) } case int(conversions.ShareTypePublicLink): // public links default to read only - if h.validatePermissions(w, r, statRes.Info, conversions.NewViewerRole().OCSPermissions()) { + if _, _, err := h.extractPermissions(w, r, statRes.Info, conversions.NewViewerRole()); err == nil { h.createPublicLinkShare(w, r, statRes.Info) } case int(conversions.ShareTypeFederatedCloudShare): // federated shares default to read only - if h.validatePermissions(w, r, statRes.Info, conversions.NewViewerRole().OCSPermissions()) { - h.createFederatedCloudShare(w, r, statRes.Info) + if role, val, err := h.extractPermissions(w, r, statRes.Info, conversions.NewViewerRole()); err == nil { + h.createFederatedCloudShare(w, r, statRes.Info, role, val) } default: response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "unknown share type", nil) } } -func (h *Handler) validatePermissions(w http.ResponseWriter, r *http.Request, ri *provider.ResourceInfo, defaultPermissions conversions.Permissions) bool { - - // 1. we start without permissions - var reqPermissions conversions.Permissions - - reqRole := r.FormValue("role") +func (h *Handler) extractPermissions(w http.ResponseWriter, r *http.Request, ri *provider.ResourceInfo, defaultPermissions *conversions.Role) (*conversions.Role, []byte, error) { + reqRole, reqPermissions := r.FormValue("role"), r.FormValue("permissions") + var role *conversions.Role + var permissions conversions.Permissions // the share role overrides the requested permissions if reqRole != "" { - reqPermissions = conversions.RoleFromName(reqRole).OCSPermissions() + role = conversions.RoleFromName(reqRole) } else { // map requested permissions - pval := r.FormValue("permissions") - if pval == "" { - // default is read permissions / role viewer + if reqPermissions == "" { // TODO default link vs user share - //reqPermissions = conversions.NewCoownerRole().OCSPermissions() - reqPermissions = defaultPermissions + role = defaultPermissions } else { - pint, err := strconv.Atoi(pval) + pint, err := strconv.Atoi(reqPermissions) if err != nil { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "permissions must be an integer", nil) - return false + return nil, nil, err } - reqPermissions, err = conversions.NewPermissions(pint) + permissions, err = conversions.NewPermissions(pint) if err != nil { if err == conversions.ErrPermissionNotInRange { response.WriteOCSError(w, r, http.StatusNotFound, err.Error(), nil) } else { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, err.Error(), nil) } - return false + return nil, nil, err } + role = conversions.RoleFromOCSPermissions(permissions) } } - if ri.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + permissions = role.OCSPermissions() + if ri != nil && ri.Type == provider.ResourceType_RESOURCE_TYPE_FILE { // Single file shares should never have delete or create permissions - reqPermissions &^= conversions.PermissionCreate - reqPermissions &^= conversions.PermissionDelete + permissions &^= conversions.PermissionCreate + permissions &^= conversions.PermissionDelete + if permissions == conversions.PermissionInvalid { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Cannot set the requested share permissions", nil) + return nil, nil, fmt.Errorf("Cannot set the requested share permissions") + } } existingPermissions := conversions.RoleFromResourcePermissions(ri.PermissionSet).OCSPermissions() - if !existingPermissions.Contain(reqPermissions) { + if permissions == conversions.PermissionInvalid || !existingPermissions.Contain(permissions) { response.WriteOCSError(w, r, http.StatusNotFound, "Cannot set the requested share permissions", nil) - return false + return nil, nil, fmt.Errorf("Cannot set the requested share permissions") + } + + role = conversions.RoleFromOCSPermissions(permissions) + roleMap := map[string]string{"name": role.Name} + val, err := json.Marshal(roleMap) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "could not encode role", err) + return nil, nil, err } - return true + + return role, val, nil } // PublicShareContextName represent cross boundaries context for the name of the public share @@ -354,7 +377,7 @@ func (h *Handler) getShare(w http.ResponseWriter, r *http.Request, shareID strin if err == nil && uRes.GetShare() != nil { resourceID = uRes.Share.ResourceId - share, err = conversions.UserShare2ShareData(ctx, uRes.Share) + share, err = conversions.CS3Share2ShareData(ctx, uRes.Share) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) return @@ -482,7 +505,7 @@ func (h *Handler) updateShare(w http.ResponseWriter, r *http.Request, shareID st return } - share, err := conversions.UserShare2ShareData(ctx, gRes.Share) + share, err := conversions.CS3Share2ShareData(ctx, gRes.Share) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) return @@ -661,9 +684,9 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { info = statRes.GetInfo() } - data, err := conversions.UserShare2ShareData(r.Context(), rs.Share) + data, err := conversions.CS3Share2ShareData(r.Context(), rs.Share) if err != nil { - log.Debug().Interface("share", rs.Share).Interface("shareData", data).Err(err).Msg("could not UserShare2ShareData, skipping") + log.Debug().Interface("share", rs.Share).Interface("shareData", data).Err(err).Msg("could not CS3Share2ShareData, skipping") continue } @@ -871,56 +894,89 @@ func (h *Handler) addFileInfo(ctx context.Context, s *conversions.ShareData, inf return nil } -// mustGetUserIdentifiers always returns a struct with identifiers, if the user could not be found they will all be empty -func (h *Handler) mustGetUserIdentifiers(ctx context.Context, c gateway.GatewayAPIClient, userid string) *userIdentifiers { - sublog := appctx.GetLogger(ctx).With().Str("userid", userid).Logger() - if userid == "" { +// mustGetIdentifiers always returns a struct with identifiers, if the user or group could not be found they will all be empty +func (h *Handler) mustGetIdentifiers(ctx context.Context, c gateway.GatewayAPIClient, id string, isGroup bool) *userIdentifiers { + sublog := appctx.GetLogger(ctx).With().Str("id", id).Logger() + if id == "" { return &userIdentifiers{} } - //item := h.userIdentifierCache.Get(userid) - ui, ok := h.userIdentifierCache.Get(userid).(*userIdentifiers) - if ok { + + idIf, err := h.userIdentifierCache.Get(id) + if err == nil { sublog.Debug().Msg("cache hit") - return ui + return idIf.(*userIdentifiers) } + sublog.Debug().Msg("cache miss") - res, err := c.GetUser(ctx, &userpb.GetUserRequest{ - UserId: &userpb.UserId{ - OpaqueId: userid, - }, - }) - if err != nil { - sublog.Err(err).Msg("could not look up user") - return &userIdentifiers{} - } - if res.GetStatus().GetCode() != rpc.Code_CODE_OK { - sublog.Err(err). - Int32("code", int32(res.GetStatus().GetCode())). - Str("message", res.GetStatus().GetMessage()). - Msg("get user call failed") - return &userIdentifiers{} - } - if res.User == nil { - sublog.Debug(). - Int32("code", int32(res.GetStatus().GetCode())). - Str("message", res.GetStatus().GetMessage()). - Msg("user not found") - return &userIdentifiers{} - } + var ui *userIdentifiers - ui = &userIdentifiers{ - DisplayName: res.User.DisplayName, - UserName: res.User.Username, - Mail: res.User.Mail, - } - h.userIdentifierCache.Put(userid, ui) - log.Debug().Str("userid", userid).Msg("cache update") + if isGroup { + res, err := c.GetGroup(ctx, &grouppb.GetGroupRequest{ + GroupId: &grouppb.GroupId{ + OpaqueId: id, + }, + }) + if err != nil { + sublog.Err(err).Msg("could not look up group") + return &userIdentifiers{} + } + if res.GetStatus().GetCode() != rpc.Code_CODE_OK { + sublog.Err(err). + Int32("code", int32(res.GetStatus().GetCode())). + Str("message", res.GetStatus().GetMessage()). + Msg("get group call failed") + return &userIdentifiers{} + } + if res.Group == nil { + sublog.Debug(). + Int32("code", int32(res.GetStatus().GetCode())). + Str("message", res.GetStatus().GetMessage()). + Msg("group not found") + return &userIdentifiers{} + } + ui = &userIdentifiers{ + DisplayName: res.Group.DisplayName, + UserName: res.Group.GroupName, + Mail: res.Group.Mail, + } + } else { + res, err := c.GetUser(ctx, &userpb.GetUserRequest{ + UserId: &userpb.UserId{ + OpaqueId: id, + }, + }) + if err != nil { + sublog.Err(err).Msg("could not look up user") + return &userIdentifiers{} + } + if res.GetStatus().GetCode() != rpc.Code_CODE_OK { + sublog.Err(err). + Int32("code", int32(res.GetStatus().GetCode())). + Str("message", res.GetStatus().GetMessage()). + Msg("get user call failed") + return &userIdentifiers{} + } + if res.User == nil { + sublog.Debug(). + Int32("code", int32(res.GetStatus().GetCode())). + Str("message", res.GetStatus().GetMessage()). + Msg("user not found") + return &userIdentifiers{} + } + ui = &userIdentifiers{ + DisplayName: res.User.DisplayName, + UserName: res.User.Username, + Mail: res.User.Mail, + } + } + _ = h.userIdentifierCache.Set(id, ui) + log.Debug().Str("id", id).Msg("cache update") return ui } func (h *Handler) mapUserIds(ctx context.Context, c gateway.GatewayAPIClient, s *conversions.ShareData) { if s.UIDOwner != "" { - owner := h.mustGetUserIdentifiers(ctx, c, s.UIDOwner) + owner := h.mustGetIdentifiers(ctx, c, s.UIDOwner, false) s.UIDOwner = owner.UserName if s.DisplaynameOwner == "" { s.DisplaynameOwner = owner.DisplayName @@ -931,7 +987,7 @@ func (h *Handler) mapUserIds(ctx context.Context, c gateway.GatewayAPIClient, s } if s.UIDFileOwner != "" { - fileOwner := h.mustGetUserIdentifiers(ctx, c, s.UIDFileOwner) + fileOwner := h.mustGetIdentifiers(ctx, c, s.UIDFileOwner, false) s.UIDFileOwner = fileOwner.UserName if s.DisplaynameFileOwner == "" { s.DisplaynameFileOwner = fileOwner.DisplayName @@ -942,7 +998,7 @@ func (h *Handler) mapUserIds(ctx context.Context, c gateway.GatewayAPIClient, s } if s.ShareWith != "" && s.ShareWith != "***redacted***" { - shareWith := h.mustGetUserIdentifiers(ctx, c, s.ShareWith) + shareWith := h.mustGetIdentifiers(ctx, c, s.ShareWith, s.ShareType == conversions.ShareTypeGroup) s.ShareWith = shareWith.UserName if s.ShareWithDisplayname == "" { s.ShareWithDisplayname = shareWith.DisplayName diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index 1ea53811df..5f615ba92e 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -19,9 +19,7 @@ package shares import ( - "encoding/json" "net/http" - "strconv" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" @@ -35,7 +33,7 @@ import ( "github.com/cs3org/reva/pkg/rgrpc/todo/pool" ) -func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statInfo *provider.ResourceInfo) { +func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statInfo *provider.ResourceInfo, role *conversions.Role, roleVal []byte) { ctx := r.Context() c, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { @@ -43,53 +41,6 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn return } - var role *conversions.Role - - reqRole := r.FormValue("role") - if reqRole != "" { - // default is all permissions / role coowner - role = conversions.RoleFromName(reqRole) - } else { - // map requested permissions - pval := r.FormValue("permissions") - if pval == "" { - // default is all permissions / role coowner - role = conversions.NewCoownerRole() - } else { - pint, err := strconv.Atoi(pval) - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "permissions must be an integer", nil) - return - } - permissions, err := conversions.NewPermissions(pint) - if err != nil { - if err == conversions.ErrPermissionNotInRange { - response.WriteOCSError(w, r, http.StatusNotFound, err.Error(), nil) - } else { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, err.Error(), nil) - } - return - } - role = conversions.RoleFromOCSPermissions(permissions) - } - } - - if statInfo != nil && statInfo.Type == provider.ResourceType_RESOURCE_TYPE_FILE { - // Single file shares should never have delete or create permissions - permissions := role.OCSPermissions() - permissions &^= conversions.PermissionCreate - permissions &^= conversions.PermissionDelete - // editor should be come a file-editor role - role = conversions.RoleFromOCSPermissions(permissions) - } - - roleMap := map[string]string{"name": role.Name} - val, err := json.Marshal(roleMap) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "could not encode role", err) - return - } - shareWith := r.FormValue("shareWith") if shareWith == "" { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "missing shareWith", nil) @@ -115,7 +66,7 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn Map: map[string]*types.OpaqueEntry{ "role": { Decoder: "json", - Value: val, + Value: roleVal, }, }, }, @@ -123,7 +74,7 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn Grant: &collaboration.ShareGrant{ Grantee: &provider.Grantee{ Type: provider.GranteeType_GRANTEE_TYPE_USER, - Id: userRes.User.GetId(), + Id: &provider.Grantee_UserId{UserId: userRes.User.GetId()}, }, Permissions: &collaboration.SharePermissions{ Permissions: role.CS3ResourcePermissions(), @@ -144,7 +95,7 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc create share request failed", err) return } - s, err := conversions.UserShare2ShareData(ctx, createShareResponse.Share) + s, err := conversions.CS3Share2ShareData(ctx, createShareResponse.Share) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err) return @@ -222,9 +173,9 @@ func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.ListS // build OCS response payload for _, s := range lsUserSharesResponse.Shares { - data, err := conversions.UserShare2ShareData(ctx, s) + data, err := conversions.CS3Share2ShareData(ctx, s) if err != nil { - log.Debug().Interface("share", s).Interface("shareData", data).Err(err).Msg("could not UserShare2ShareData, skipping") + log.Debug().Interface("share", s).Interface("shareData", data).Err(err).Msg("could not CS3Share2ShareData, skipping") continue } diff --git a/pkg/cbox/group/rest/rest.go b/pkg/cbox/group/rest/rest.go index 9abf4217e9..9c6f7371eb 100644 --- a/pkg/cbox/group/rest/rest.go +++ b/pkg/cbox/group/rest/rest.go @@ -72,7 +72,7 @@ type config struct { // The password for connecting to the redis server RedisPassword string `mapstructure:"redis_password" docs:""` // The time in minutes for which the members of a group would be cached - GroupMembersCacheExpiration int `mapstructure:"user_groups_cache_expiration" docs:"5"` + GroupMembersCacheExpiration int `mapstructure:"group_members_cache_expiration" docs:"5"` // The OIDC Provider IDProvider string `mapstructure:"id_provider" docs:"http://cernbox.cern.ch"` // Base API Endpoint @@ -136,10 +136,10 @@ func New(m map[string]interface{}) (group.Manager, error) { }, nil } -func (m *manager) renewAPIToken(ctx context.Context) error { +func (m *manager) renewAPIToken(ctx context.Context, forceRenewal bool) error { // Received tokens have an expiration time of 20 minutes. // Take a couple of seconds as buffer time for the API call to complete - if m.oidcToken.tokenExpirationTime.Before(time.Now().Add(time.Second * time.Duration(2))) { + if forceRenewal || m.oidcToken.tokenExpirationTime.Before(time.Now().Add(time.Second*time.Duration(2))) { token, expiration, err := m.getAPIToken(ctx) if err != nil { return err @@ -178,6 +178,9 @@ func (m *manager) getAPIToken(ctx context.Context) (string, time.Time, error) { if err != nil { return "", time.Time{}, err } + if httpRes.StatusCode < 200 || httpRes.StatusCode > 299 { + return "", time.Time{}, errors.New("rest: get token endpoint returned " + httpRes.Status) + } var result map[string]interface{} err = json.Unmarshal(body, &result) @@ -190,8 +193,8 @@ func (m *manager) getAPIToken(ctx context.Context) (string, time.Time, error) { return result["access_token"].(string), expirationTime, nil } -func (m *manager) sendAPIRequest(ctx context.Context, url string) ([]interface{}, error) { - err := m.renewAPIToken(ctx) +func (m *manager) sendAPIRequest(ctx context.Context, url string, forceRenewal bool) ([]interface{}, error) { + err := m.renewAPIToken(ctx, forceRenewal) if err != nil { return nil, err } @@ -212,6 +215,14 @@ func (m *manager) sendAPIRequest(ctx context.Context, url string) ([]interface{} } defer httpRes.Body.Close() + if httpRes.StatusCode == http.StatusUnauthorized { + // The token is no longer valid, try renewing it + return m.sendAPIRequest(ctx, url, true) + } + if httpRes.StatusCode < 200 || httpRes.StatusCode > 299 { + return nil, errors.New("rest: API request returned " + httpRes.Status) + } + body, err := ioutil.ReadAll(httpRes.Body) if err != nil { return nil, err @@ -234,12 +245,12 @@ func (m *manager) sendAPIRequest(ctx context.Context, url string) ([]interface{} func (m *manager) getGroupByParam(ctx context.Context, param, val string) (map[string]interface{}, error) { url := fmt.Sprintf("%s/Group?filter=%s:%s&field=groupIdentifier&field=displayName&field=gid", m.conf.APIBaseURL, param, val) - responseData, err := m.sendAPIRequest(ctx, url) + responseData, err := m.sendAPIRequest(ctx, url, false) if err != nil { return nil, err } - if len(responseData) == 0 { - return nil, errors.New("rest: no user found") + if len(responseData) != 1 { + return nil, errors.New("rest: group not found") } userData, ok := responseData[0].(map[string]interface{}) @@ -356,7 +367,7 @@ func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string) (*gr func (m *manager) findGroupsByFilter(ctx context.Context, url string, groups map[string]*grouppb.Group) error { - groupData, err := m.sendAPIRequest(ctx, url) + groupData, err := m.sendAPIRequest(ctx, url, false) if err != nil { return err } @@ -431,7 +442,7 @@ func (m *manager) GetMembers(ctx context.Context, gid *grouppb.GroupId) ([]*user return nil, err } url := fmt.Sprintf("%s/Group/%s/memberidentities/precomputed", m.conf.APIBaseURL, internalID) - userData, err := m.sendAPIRequest(ctx, url) + userData, err := m.sendAPIRequest(ctx, url, false) if err != nil { return nil, err } diff --git a/pkg/cbox/share/sql/conversions.go b/pkg/cbox/share/sql/conversions.go index 01ebbbf0b4..5e24fdea75 100644 --- a/pkg/cbox/share/sql/conversions.go +++ b/pkg/cbox/share/sql/conversions.go @@ -22,32 +22,42 @@ import ( "fmt" "strings" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) -func granteeTypeToInt(g provider.GranteeType) int { - switch g { +func formatGrantee(g *provider.Grantee) (int, string) { + var granteeType int + var formattedID string + switch g.Type { case provider.GranteeType_GRANTEE_TYPE_USER: - return 0 + granteeType = 0 + formattedID = formatUserID(g.GetUserId()) case provider.GranteeType_GRANTEE_TYPE_GROUP: - return 1 + granteeType = 1 + formattedID = formatGroupID(g.GetGroupId()) default: - return -1 + granteeType = -1 } + return granteeType, formattedID } -func intToGranteeType(g int) provider.GranteeType { - switch g { +func extractGrantee(t int, g string) *provider.Grantee { + var grantee *provider.Grantee + switch t { case 0: - return provider.GranteeType_GRANTEE_TYPE_USER + grantee.Type = provider.GranteeType_GRANTEE_TYPE_USER + grantee.Id = &provider.Grantee_UserId{UserId: extractUserID(g)} case 1: - return provider.GranteeType_GRANTEE_TYPE_GROUP + grantee.Type = provider.GranteeType_GRANTEE_TYPE_GROUP + grantee.Id = &provider.Grantee_GroupId{GroupId: extractGroupID(g)} default: - return provider.GranteeType_GRANTEE_TYPE_INVALID + grantee.Type = provider.GranteeType_GRANTEE_TYPE_INVALID } + return grantee } func resourceTypeToItem(r provider.ResourceType) string { @@ -138,6 +148,21 @@ func extractUserID(u string) *userpb.UserId { return &userpb.UserId{OpaqueId: parts[0]} } +func formatGroupID(u *grouppb.GroupId) string { + if u.Idp != "" { + return fmt.Sprintf("%s:%s", u.OpaqueId, u.Idp) + } + return u.OpaqueId +} + +func extractGroupID(u string) *grouppb.GroupId { + parts := strings.Split(u, ":") + if len(parts) > 1 { + return &grouppb.GroupId{OpaqueId: parts[0], Idp: parts[1]} + } + return &grouppb.GroupId{OpaqueId: parts[0]} +} + func convertToCS3Share(s dbShare) *collaboration.Share { ts := &typespb.Timestamp{ Seconds: uint64(s.STime), @@ -148,7 +173,7 @@ func convertToCS3Share(s dbShare) *collaboration.Share { }, ResourceId: &provider.ResourceId{OpaqueId: s.ItemSource, StorageId: s.Prefix}, Permissions: &collaboration.SharePermissions{Permissions: intTosharePerm(s.Permissions)}, - Grantee: &provider.Grantee{Type: intToGranteeType(s.ShareType), Id: extractUserID(s.ShareWith)}, + Grantee: extractGrantee(s.ShareType, s.ShareWith), Owner: extractUserID(s.UIDOwner), Creator: extractUserID(s.UIDInitiator), Ctime: ts, diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index d5c6238055..62b2fbf2c1 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -34,6 +34,7 @@ import ( "github.com/cs3org/reva/pkg/share" "github.com/cs3org/reva/pkg/share/manager/registry" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -105,9 +106,8 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora // do not allow share to myself or the owner if share is for a user // TODO(labkode): should not this be caught already at the gw level? if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && - ((g.Grantee.Id.Idp == user.Id.Idp && g.Grantee.Id.OpaqueId == user.Id.OpaqueId) || - (g.Grantee.Id.Idp == md.Owner.Idp && g.Grantee.Id.OpaqueId == md.Owner.OpaqueId)) { - return nil, errors.New("json: owner/creator and grantee are the same") + (utils.UserEqual(g.Grantee.GetUserId(), user.Id) || utils.UserEqual(g.Grantee.GetUserId(), md.Owner)) { + return nil, errors.New("sql: owner/creator and grantee are the same") } // check if share already exists. @@ -128,7 +128,7 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora Seconds: uint64(now), } - shareType := granteeTypeToInt(g.Grantee.Type) + shareType, shareWith := formatGrantee(g.Grantee) itemType := resourceTypeToItem(md.Type) targetPath := path.Join("/", path.Base(md.Path)) permissions := sharePermToInt(g.Permissions.Permissions) @@ -142,7 +142,7 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora } stmtString := "insert into oc_share set share_type=?,uid_owner=?,uid_initiator=?,item_type=?,fileid_prefix=?,item_source=?,file_source=?,permissions=?,stime=?,share_with=?,file_target=?" - stmtValues := []interface{}{shareType, formatUserID(md.Owner), formatUserID(user.Id), itemType, prefix, itemSource, fileSource, permissions, now, formatUserID(g.Grantee.Id), targetPath} + stmtValues := []interface{}{shareType, formatUserID(md.Owner), formatUserID(user.Id), itemType, prefix, itemSource, fileSource, permissions, now, shareWith, targetPath} stmt, err := m.db.Prepare(stmtString) if err != nil { @@ -185,8 +185,9 @@ func (m *mgr) getByID(ctx context.Context, id *collaboration.ShareId) (*collabor func (m *mgr) getByKey(ctx context.Context, key *collaboration.ShareKey) (*collaboration.Share, error) { s := dbShare{} + shareType, shareWith := formatGrantee(key.Grantee) query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, id, stime, permissions, share_type FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner=? or uid_initiator=?) AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=?" - if err := m.db.QueryRow(query, formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, granteeTypeToInt(key.Grantee.Type), formatUserID(key.Grantee.Id)).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { + if err := m.db.QueryRow(query, formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { if err == sql.ErrNoRows { return nil, errtypes.NotFound(key.String()) } @@ -213,8 +214,7 @@ func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) ( // check if we are the owner user := user.ContextMustGetUser(ctx) - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { return s, nil } @@ -236,8 +236,9 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er if key.Owner != user.Id { return errtypes.NotFound(ref.String()) } + shareType, shareWith := formatGrantee(key.Grantee) query = "delete from oc_share where (uid_owner=? or uid_initiator=?) AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=?" - params = append(params, formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, granteeTypeToInt(key.Grantee.Type), formatUserID(key.Grantee.Id)) + params = append(params, formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith) default: return errtypes.NotFound(ref.String()) } @@ -276,8 +277,9 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference if key.Owner != user.Id { return nil, errtypes.NotFound(ref.String()) } + shareType, shareWith := formatGrantee(key.Grantee) query = "update oc_share set permissions=?,stime=? where (uid_owner=? or uid_initiator=?) AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=?" - params = append(params, permissions, time.Now().Unix(), formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, granteeTypeToInt(key.Grantee.Type), formatUserID(key.Grantee.Id)) + params = append(params, permissions, time.Now().Unix(), formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith) default: return nil, errtypes.NotFound(ref.String()) } @@ -395,7 +397,8 @@ func (m *mgr) getReceivedByID(ctx context.Context, id *collaboration.ShareId) (* func (m *mgr) getReceivedByKey(ctx context.Context, key *collaboration.ShareKey) (*collaboration.ReceivedShare, error) { s := dbShare{} query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, id, stime, permissions, share_type, accepted FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner=? or uid_initiator=?) AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=? AND id not in (SELECT distinct(id) FROM oc_share_acl WHERE rejected_by=?)" - if err := m.db.QueryRow(query, formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, granteeTypeToInt(key.Grantee.Type), formatUserID(key.Grantee.Id), formatUserID(key.Grantee.Id)).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { + shareType, shareWith := formatGrantee(key.Grantee) + if err := m.db.QueryRow(query, formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith, shareWith).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { if err == sql.ErrNoRows { return nil, errtypes.NotFound(key.String()) } @@ -422,13 +425,13 @@ func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareRefe } user := user.ContextMustGetUser(ctx) - if s.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && s.Share.Grantee.Id.OpaqueId == user.Id.OpaqueId && s.Share.Grantee.Id.Idp == user.Id.Idp { + if s.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Share.Grantee.GetUserId()) { return s, nil } if s.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { for _, v := range user.Groups { - if s.Share.Grantee.Id.OpaqueId == v { + if s.Share.Grantee.GetGroupId().OpaqueId == v { return s, nil } } diff --git a/pkg/cbox/user/rest/rest.go b/pkg/cbox/user/rest/rest.go index 23689e79ac..b01d3519e1 100644 --- a/pkg/cbox/user/rest/rest.go +++ b/pkg/cbox/user/rest/rest.go @@ -136,10 +136,10 @@ func New(m map[string]interface{}) (user.Manager, error) { }, nil } -func (m *manager) renewAPIToken(ctx context.Context) error { +func (m *manager) renewAPIToken(ctx context.Context, forceRenewal bool) error { // Received tokens have an expiration time of 20 minutes. // Take a couple of seconds as buffer time for the API call to complete - if m.oidcToken.tokenExpirationTime.Before(time.Now().Add(time.Second * time.Duration(2))) { + if forceRenewal || m.oidcToken.tokenExpirationTime.Before(time.Now().Add(time.Second*time.Duration(2))) { token, expiration, err := m.getAPIToken(ctx) if err != nil { return err @@ -173,6 +173,9 @@ func (m *manager) getAPIToken(ctx context.Context) (string, time.Time, error) { return "", time.Time{}, err } defer httpRes.Body.Close() + if httpRes.StatusCode < 200 || httpRes.StatusCode > 299 { + return "", time.Time{}, errors.New("rest: get token endpoint returned " + httpRes.Status) + } body, err := ioutil.ReadAll(httpRes.Body) if err != nil { @@ -190,8 +193,8 @@ func (m *manager) getAPIToken(ctx context.Context) (string, time.Time, error) { return result["access_token"].(string), expirationTime, nil } -func (m *manager) sendAPIRequest(ctx context.Context, url string) ([]interface{}, error) { - err := m.renewAPIToken(ctx) +func (m *manager) sendAPIRequest(ctx context.Context, url string, forceRenewal bool) ([]interface{}, error) { + err := m.renewAPIToken(ctx, forceRenewal) if err != nil { return nil, err } @@ -212,6 +215,14 @@ func (m *manager) sendAPIRequest(ctx context.Context, url string) ([]interface{} } defer httpRes.Body.Close() + if httpRes.StatusCode == http.StatusUnauthorized { + // The token is no longer valid, try renewing it + return m.sendAPIRequest(ctx, url, true) + } + if httpRes.StatusCode < 200 || httpRes.StatusCode > 299 { + return nil, errors.New("rest: API request returned " + httpRes.Status) + } + body, err := ioutil.ReadAll(httpRes.Body) if err != nil { return nil, err @@ -232,20 +243,24 @@ func (m *manager) sendAPIRequest(ctx context.Context, url string) ([]interface{} } func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[string]interface{}, error) { - url := fmt.Sprintf("%s/Identity?filter=%s:%s&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid", + url := fmt.Sprintf("%s/Identity?filter=%s:%s&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid&field=type", m.conf.APIBaseURL, param, val) - responseData, err := m.sendAPIRequest(ctx, url) + responseData, err := m.sendAPIRequest(ctx, url, false) if err != nil { return nil, err } - if len(responseData) == 0 { - return nil, errors.New("rest: no user found") + if len(responseData) != 1 { + return nil, errors.New("rest: user not found") } userData, ok := responseData[0].(map[string]interface{}) if !ok { return nil, errors.New("rest: error in type assertion") } + + if userData["type"].(string) == "Application" || strings.HasPrefix(userData["upn"].(string), "guest") { + return nil, errors.New("rest: guest and application accounts not supported") + } return userData, nil } @@ -362,7 +377,7 @@ func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*use func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[string]*userpb.User) error { - userData, err := m.sendAPIRequest(ctx, url) + userData, err := m.sendAPIRequest(ctx, url, false) if err != nil { return err } @@ -372,6 +387,9 @@ func (m *manager) findUsersByFilter(ctx context.Context, url string, users map[s if !ok { return errors.New("rest: error in type assertion") } + if usrInfo["type"].(string) == "Application" || strings.HasPrefix(usrInfo["upn"].(string), "guest") { + continue + } uid := &userpb.UserId{ OpaqueId: usrInfo["upn"].(string), @@ -415,7 +433,7 @@ func (m *manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, users := make(map[string]*userpb.User) for _, f := range filters { - url := fmt.Sprintf("%s/Identity/?filter=%s:contains:%s&field=id&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid", + url := fmt.Sprintf("%s/Identity/?filter=%s:contains:%s&field=id&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid&field=type", m.conf.APIBaseURL, f, query) err := m.findUsersByFilter(ctx, url, users) if err != nil { @@ -443,7 +461,7 @@ func (m *manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]stri return nil, err } url := fmt.Sprintf("%s/Identity/%s/groups", m.conf.APIBaseURL, internalID) - groupData, err := m.sendAPIRequest(ctx, url) + groupData, err := m.sendAPIRequest(ctx, url, false) if err != nil { return nil, err } diff --git a/pkg/group/manager/loader/loader.go b/pkg/group/manager/loader/loader.go index 18c485ab76..04d40f66be 100644 --- a/pkg/group/manager/loader/loader.go +++ b/pkg/group/manager/loader/loader.go @@ -21,5 +21,6 @@ package loader import ( // Load core group manager drivers. _ "github.com/cs3org/reva/pkg/group/manager/json" + _ "github.com/cs3org/reva/pkg/group/manager/ldap" // Add your own here ) diff --git a/pkg/ocm/share/manager/json/json.go b/pkg/ocm/share/manager/json/json.go index dd77c14fcc..2f16661e88 100644 --- a/pkg/ocm/share/manager/json/json.go +++ b/pkg/ocm/share/manager/json/json.go @@ -27,7 +27,6 @@ import ( "net/url" "os" "path" - "reflect" "strings" "sync" "time" @@ -43,6 +42,7 @@ import ( "github.com/cs3org/reva/pkg/rhttp" tokenpkg "github.com/cs3org/reva/pkg/token" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -234,8 +234,7 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceId, g *ocm.ShareGr } // do not allow share to myself if share is for a user - if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && - g.Grantee.Id.Idp == userID.Idp && g.Grantee.Id.OpaqueId == userID.OpaqueId { + if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(g.Grantee.GetUserId(), userID) { return nil, errors.New("json: user and grantee are the same") } @@ -284,7 +283,7 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceId, g *ocm.ShareGr } requestBody := url.Values{ - "shareWith": {g.Grantee.Id.OpaqueId}, + "shareWith": {g.Grantee.GetUserId().OpaqueId}, "name": {name}, "providerId": {fmt.Sprintf("%s:%s", md.StorageId, md.OpaqueId)}, "owner": {userID.OpaqueId}, @@ -374,9 +373,8 @@ func (m *mgr) getByKey(ctx context.Context, key *ocm.ShareKey) (*ocm.Share, erro } for _, s := range m.model.Shares { - if key.Owner.Idp == s.Owner.Idp && key.Owner.OpaqueId == s.Owner.OpaqueId && - key.ResourceId.StorageId == s.ResourceId.StorageId && key.ResourceId.OpaqueId == s.ResourceId.OpaqueId && - key.Grantee.Type == s.Grantee.Type && key.Grantee.Id.Idp == s.Grantee.Id.Idp && key.Grantee.Id.OpaqueId == s.Grantee.Id.OpaqueId { + if (utils.UserEqual(key.Owner, s.Owner) || utils.UserEqual(key.Owner, s.Creator)) && + utils.ResourceEqual(key.ResourceId, s.ResourceId) && utils.GranteeEqual(key.Grantee, s.Grantee) { return s, nil } } @@ -398,9 +396,8 @@ func (m *mgr) get(ctx context.Context, ref *ocm.ShareReference) (s *ocm.Share, e } // check if we are the owner - // TODO(labkode): check for creator also. user := user.ContextMustGetUser(ctx) - if user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { return s, nil } @@ -428,8 +425,8 @@ func (m *mgr) Unshare(ctx context.Context, ref *ocm.ShareReference) error { user := user.ContextMustGetUser(ctx) for i, s := range m.model.Shares { - if equal(ref, s) { - if user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId { + if sharesEqual(ref, s) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { m.model.Shares[len(m.model.Shares)-1], m.model.Shares[i] = m.model.Shares[i], m.model.Shares[len(m.model.Shares)-1] m.model.Shares = m.model.Shares[:len(m.model.Shares)-1] if err := m.model.Save(); err != nil { @@ -443,13 +440,14 @@ func (m *mgr) Unshare(ctx context.Context, ref *ocm.ShareReference) error { return errtypes.NotFound(ref.String()) } -func equal(ref *ocm.ShareReference, s *ocm.Share) bool { +func sharesEqual(ref *ocm.ShareReference, s *ocm.Share) bool { if ref.GetId() != nil && s.Id != nil { if ref.GetId().OpaqueId == s.Id.OpaqueId { return true } } else if ref.GetKey() != nil { - if reflect.DeepEqual(*ref.GetKey().Owner, *s.Owner) && reflect.DeepEqual(*ref.GetKey().ResourceId, *s.ResourceId) && reflect.DeepEqual(*ref.GetKey().Grantee, *s.Grantee) { + if (utils.UserEqual(ref.GetKey().Owner, s.Owner) || utils.UserEqual(ref.GetKey().Owner, s.Creator)) && + utils.ResourceEqual(ref.GetKey().ResourceId, s.ResourceId) && utils.GranteeEqual(ref.GetKey().Grantee, s.Grantee) { return true } } @@ -467,8 +465,8 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *ocm.ShareReference, p *ocm.S user := user.ContextMustGetUser(ctx) for i, s := range m.model.Shares { - if equal(ref, s) { - if user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId { + if sharesEqual(ref, s) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { now := time.Now().UnixNano() m.model.Shares[i].Permissions = p m.model.Shares[i].Mtime = &typespb.Timestamp{ @@ -498,8 +496,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*ocm.ListOCMSharesReques user := user.ContextMustGetUser(ctx) for _, s := range m.model.Shares { - // TODO(labkode): add check for creator. - if user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { // no filter we return earlier if len(filters) == 0 { ss = append(ss, s) @@ -531,20 +528,17 @@ func (m *mgr) ListReceivedShares(ctx context.Context) ([]*ocm.ReceivedShare, err user := user.ContextMustGetUser(ctx) for _, s := range m.model.ReceivedShares { - if user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { // omit shares created by me - // TODO(labkode): apply check for s.Creator also. continue } - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { - if user.Id.Idp == s.Grantee.Id.Idp && user.Id.OpaqueId == s.Grantee.Id.OpaqueId { - rs := m.convert(ctx, s) - rss = append(rss, rs) - } + if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { + rs := m.convert(ctx, s) + rss = append(rss, rs) } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { // check if all user groups match this share; TODO(labkode): filter shares created by us. for _, g := range user.Groups { - if g == s.Grantee.Id.OpaqueId { + if g == s.Grantee.GetGroupId().OpaqueId { rs := m.convert(ctx, s) rss = append(rss, rs) break @@ -585,14 +579,13 @@ func (m *mgr) getReceived(ctx context.Context, ref *ocm.ShareReference) (*ocm.Re user := user.ContextMustGetUser(ctx) for _, s := range m.model.ReceivedShares { - if equal(ref, s) { - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && - s.Grantee.Id.Idp == user.Id.Idp && s.Grantee.Id.OpaqueId == user.Id.OpaqueId { + if sharesEqual(ref, s) { + if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { rs := m.convert(ctx, s) return rs, nil } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { for _, g := range user.Groups { - if s.Grantee.Id.OpaqueId == g { + if s.Grantee.GetGroupId().OpaqueId == g { rs := m.convert(ctx, s) return rs, nil } diff --git a/pkg/ocm/share/manager/loader/loader.go b/pkg/ocm/share/manager/loader/loader.go index 817a1270b4..f3f7587308 100644 --- a/pkg/ocm/share/manager/loader/loader.go +++ b/pkg/ocm/share/manager/loader/loader.go @@ -21,6 +21,5 @@ package loader import ( // Load core share manager drivers. _ "github.com/cs3org/reva/pkg/ocm/share/manager/json" - _ "github.com/cs3org/reva/pkg/ocm/share/manager/memory" // Add your own here ) diff --git a/pkg/ocm/share/manager/memory/memory.go b/pkg/ocm/share/manager/memory/memory.go deleted file mode 100644 index 9473d4b62b..0000000000 --- a/pkg/ocm/share/manager/memory/memory.go +++ /dev/null @@ -1,540 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package memory - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "path" - "reflect" - "strings" - "sync" - "time" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" - ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/ocm/share" - "github.com/cs3org/reva/pkg/rhttp" - tokenpkg "github.com/cs3org/reva/pkg/token" - "github.com/cs3org/reva/pkg/user" - "github.com/google/uuid" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" -) - -const createOCMCoreShareEndpoint = "shares" - -func init() { - // Don't use memory driver as we can't retrieve received shares. - // registry.Register("memory", New) -} - -// New returns a new memory manager. -func New(m map[string]interface{}) (share.Manager, error) { - c, err := parseConfig(m) - if err != nil { - err = errors.Wrap(err, "error creating a new manager") - return nil, err - } - - state := make(map[string]map[string]ocm.ShareState) - return &mgr{ - c: c, - shares: sync.Map{}, - state: state, - client: rhttp.GetHTTPClient( - rhttp.Timeout(5 * time.Second), - ), - }, nil -} - -type mgr struct { - c *config - shares sync.Map - state map[string]map[string]ocm.ShareState - client *http.Client -} - -type config struct { - InsecureConnections bool `mapstructure:"insecure_connections"` -} - -func parseConfig(m map[string]interface{}) (*config, error) { - c := &config{} - if err := mapstructure.Decode(m, c); err != nil { - return nil, err - } - return c, nil -} - -func genID() string { - return uuid.New().String() -} - -func getOCMEndpoint(originProvider *ocmprovider.ProviderInfo) (string, error) { - for _, s := range originProvider.Services { - if s.Endpoint.Type.Name == "OCM" { - return s.Endpoint.Path, nil - } - } - return "", errors.New("memory: ocm endpoint not specified for mesh provider") -} - -func (m *mgr) Share(ctx context.Context, md *provider.ResourceId, g *ocm.ShareGrant, name string, - pi *ocmprovider.ProviderInfo, pm string, owner *userpb.UserId, token string) (*ocm.Share, error) { - - id := genID() - now := time.Now().UnixNano() - ts := &typespb.Timestamp{ - Seconds: uint64(now / 1000000000), - Nanos: uint32(now % 1000000000), - } - - // Since both OCMCore and OCMShareProvider use the same package, we distinguish - // between calls received from them on the basis of whether they provide info - // about the remote provider on which the share is to be created. - // If this info is provided, this call is on the owner's mesh provider and so - // we call the CreateOCMCoreShare method on the remote provider as well, - // else this is received from another provider and we only create a local share. - var isOwnersMeshProvider bool - if pi != nil { - isOwnersMeshProvider = true - } - var userID *userpb.UserId - if !isOwnersMeshProvider { - if owner == nil { - return nil, errors.New("json: owner of resource not provided") - } - userID = owner - g.Grantee.Opaque = &typespb.Opaque{ - Map: map[string]*typespb.OpaqueEntry{ - "token": &typespb.OpaqueEntry{ - Decoder: "plain", - Value: []byte(token), - }, - }, - } - } else { - userID = user.ContextMustGetUser(ctx).GetId() - } - - // do not allow share to myself if share is for a user - if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && - g.Grantee.Id.Idp == userID.Idp && g.Grantee.Id.OpaqueId == userID.OpaqueId { - return nil, errors.New("json: user and grantee are the same") - } - - // check if share already exists. - key := &ocm.ShareKey{ - Owner: userID, - ResourceId: md, - Grantee: g.Grantee, - } - - // share already exists - _, ok := m.shares.Load(key) - if isOwnersMeshProvider && ok { - return nil, errtypes.AlreadyExists(key.String()) - } - - // Store share - s := &ocm.Share{ - Id: &ocm.ShareId{ - OpaqueId: id, - }, - Name: name, - ResourceId: md, - Permissions: g.Permissions, - Grantee: g.Grantee, - Owner: userID, - Creator: userID, - Ctime: ts, - Mtime: ts, - } - - m.shares.Store(key, s) - - if isOwnersMeshProvider { - - protocol, err := json.Marshal( - map[string]interface{}{ - "name": "webdav", - "options": map[string]string{ - "permissions": pm, - "token": tokenpkg.ContextMustGetToken(ctx), - }, - }, - ) - if err != nil { - err = errors.Wrap(err, "error marshalling protocol data") - return nil, err - } - - requestBody := url.Values{ - "shareWith": {g.Grantee.Id.OpaqueId}, - "name": {name}, - "providerId": {fmt.Sprintf("%s:%s", md.StorageId, md.OpaqueId)}, - "owner": {userID.OpaqueId}, - "protocol": {string(protocol)}, - "meshProvider": {userID.Idp}, - } - - ocmEndpoint, err := getOCMEndpoint(pi) - if err != nil { - return nil, err - } - u, err := url.Parse(ocmEndpoint) - if err != nil { - return nil, err - } - u.Path = path.Join(u.Path, createOCMCoreShareEndpoint) - recipientURL := u.String() - - req, err := http.NewRequest("POST", recipientURL, strings.NewReader(requestBody.Encode())) - if err != nil { - return nil, errors.Wrap(err, "json: error framing post request") - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") - - resp, err := m.client.Do(req) - if err != nil { - err = errors.Wrap(err, "memory: error sending post request") - return nil, err - } - - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - err = errors.Wrap(errors.New(resp.Status), "memory: error sending create ocm core share post request") - return nil, err - } - } - - return s, nil -} - -func (m *mgr) GetShare(ctx context.Context, ref *ocm.ShareReference) (s *ocm.Share, err error) { - - switch { - case ref.GetId() != nil: - s, err = m.getByID(ctx, ref.GetId()) - case ref.GetKey() != nil: - s, err = m.getByKey(ctx, ref.GetKey()) - default: - err = errtypes.NotFound(ref.String()) - } - - if err != nil { - return nil, err - } - - // check if we are the owner - user := user.ContextMustGetUser(ctx) - if user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId { - return s, nil - } - - // we return not found to not disclose information - return nil, errtypes.NotFound(ref.String()) -} - -func (m *mgr) getByID(ctx context.Context, id *ocm.ShareId) (*ocm.Share, error) { - - // iterate over existing shares and return the first one matching the id - var found *ocm.Share - m.shares.Range(func(k, v interface{}) bool { - - s := v.(*ocm.Share) - - if s.GetId().OpaqueId == id.OpaqueId { - found = v.(*ocm.Share) - return true - } - - return false - }) - - if found != nil { - return found, nil - } - return nil, errtypes.NotFound(id.String()) -} - -func (m *mgr) getByKey(ctx context.Context, key *ocm.ShareKey) (*ocm.Share, error) { - - // iterate over existing shares and return the first one matching the key - var found *ocm.Share - m.shares.Range(func(k, v interface{}) bool { - - s := v.(*ocm.Share) - - if key.Owner.Idp == s.Owner.Idp && key.Owner.OpaqueId == s.Owner.OpaqueId && - key.ResourceId.StorageId == s.ResourceId.StorageId && key.ResourceId.OpaqueId == s.ResourceId.OpaqueId && - key.Grantee.Type == s.Grantee.Type && key.Grantee.Id.Idp == s.Grantee.Id.Idp && key.Grantee.Id.OpaqueId == s.Grantee.Id.OpaqueId { - - found = v.(*ocm.Share) - return true - } - - return false - }) - - if found != nil { - return found, nil - } - - return nil, errtypes.NotFound(key.String()) -} - -func (m *mgr) Unshare(ctx context.Context, ref *ocm.ShareReference) error { - - var ctxUser = user.ContextMustGetUser(ctx) - var key *ocm.ShareKey - - m.shares.Range(func(k, v interface{}) bool { - - s := v.(*ocm.Share) - - if equal(ref, s) { - if ctxUser.Id.Idp == s.Owner.Idp && ctxUser.Id.OpaqueId == s.Owner.OpaqueId { - key = &ocm.ShareKey{ - Owner: ctxUser.Id, - ResourceId: s.ResourceId, - Grantee: s.Grantee, - } - return true - } - } - return false - }) - - if key != nil { - m.shares.Delete(key) - return nil - } - - return errtypes.NotFound(ref.String()) -} - -func equal(ref *ocm.ShareReference, s *ocm.Share) bool { - if ref.GetId() != nil && s.Id != nil { - if ref.GetId().OpaqueId == s.Id.OpaqueId { - return true - } - } else if ref.GetKey() != nil { - if reflect.DeepEqual(*ref.GetKey().Owner, *s.Owner) && reflect.DeepEqual(*ref.GetKey().ResourceId, *s.ResourceId) && reflect.DeepEqual(*ref.GetKey().Grantee, *s.Grantee) { - return true - } - } - return false -} - -func (m *mgr) UpdateShare(ctx context.Context, ref *ocm.ShareReference, p *ocm.SharePermissions) (*ocm.Share, error) { - - var user = user.ContextMustGetUser(ctx) - var key *ocm.ShareKey - - m.shares.Range(func(k, v interface{}) bool { - - s := v.(*ocm.Share) - - if equal(ref, s) { - if user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId { - key = &ocm.ShareKey{ - Owner: user.Id, - ResourceId: s.ResourceId, - Grantee: s.Grantee, - } - return true - } - } - return false - }) - - if key != nil { - - s, ok := m.shares.Load(key) - if ok { - - now := time.Now().UnixNano() - share := s.(*ocm.Share) - - share.Permissions = p - share.Mtime = &typespb.Timestamp{ - Seconds: uint64(now / 1000000000), - Nanos: uint32(now % 1000000000), - } - - m.shares.Delete(key) - m.shares.Store(key, share) - return share, nil - } - } - - return nil, errtypes.NotFound(ref.String()) -} - -func (m *mgr) ListShares(ctx context.Context, filters []*ocm.ListOCMSharesRequest_Filter) ([]*ocm.Share, error) { - - user := user.ContextMustGetUser(ctx) - shares, err := m.listShares(user, filters) - return shares, err -} - -func (m *mgr) listShares(user *userpb.User, filters []*ocm.ListOCMSharesRequest_Filter) ([]*ocm.Share, error) { - var shares []*ocm.Share - m.shares.Range(func(k, v interface{}) bool { - - s := v.(*ocm.Share) - - if user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId { - // no filter we return earlier - if len(filters) == 0 { - shares = append(shares, s) - } else { - - // check filters - for _, f := range filters { - if f.Type == ocm.ListOCMSharesRequest_Filter_TYPE_RESOURCE_ID { - if s.ResourceId.StorageId == f.GetResourceId().StorageId && s.ResourceId.OpaqueId == f.GetResourceId().OpaqueId { - shares = append(shares, s) - } - } - } - } - } - - return true - }) - - return shares, nil -} - -func (m *mgr) ListReceivedShares(ctx context.Context) ([]*ocm.ReceivedShare, error) { - - var receivedShares []*ocm.ReceivedShare - user := user.ContextMustGetUser(ctx) - - m.shares.Range(func(k, v interface{}) bool { - - s := v.(*ocm.Share) - - if user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId { - // omit shares created by me - // TODO(labkode): apply check for s.Creator also. - return true - } - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { - if user.Id.Idp == s.Grantee.Id.Idp && user.Id.OpaqueId == s.Grantee.Id.OpaqueId { - rs := m.convert(ctx, s) - receivedShares = append(receivedShares, rs) - } - } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - // check if all user groups match this share; - // TODO(labkode): filter shares created by us. - for _, g := range user.Groups { - if g == s.Grantee.Id.OpaqueId { - rs := m.convert(ctx, s) - receivedShares = append(receivedShares, rs) - } - } - } - - return true - }) - - return receivedShares, nil -} - -// convert must be called in a lock-controlled block. -func (m *mgr) convert(ctx context.Context, s *ocm.Share) *ocm.ReceivedShare { - rs := &ocm.ReceivedShare{ - Share: s, - State: ocm.ShareState_SHARE_STATE_PENDING, - } - user := user.ContextMustGetUser(ctx) - if v, ok := m.state[user.Id.String()]; ok { - if state, ok := v[s.Id.String()]; ok { - rs.State = state - } - } - return rs -} - -func (m *mgr) GetReceivedShare(ctx context.Context, ref *ocm.ShareReference) (*ocm.ReceivedShare, error) { - - user := user.ContextMustGetUser(ctx) - - var found *ocm.ReceivedShare - m.shares.Range(func(k, v interface{}) bool { - - s := v.(*ocm.Share) - - if equal(ref, s) { - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && - s.Grantee.Id.Idp == user.Id.Idp && s.Grantee.Id.OpaqueId == user.Id.OpaqueId { - found = m.convert(ctx, s) - return true - } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - for _, g := range user.Groups { - if s.Grantee.Id.OpaqueId == g { - found = m.convert(ctx, s) - return true - } - } - } - } - - return false - }) - - if found != nil { - return found, nil - } - - return nil, errtypes.NotFound(ref.String()) -} - -func (m *mgr) UpdateReceivedShare(ctx context.Context, ref *ocm.ShareReference, f *ocm.UpdateReceivedOCMShareRequest_UpdateField) (*ocm.ReceivedShare, error) { - - rs, err := m.GetReceivedShare(ctx, ref) - if err != nil { - return nil, err - } - - user := user.ContextMustGetUser(ctx) - - if v, ok := m.state[user.Id.String()]; ok { - v[rs.Share.Id.String()] = f.GetState() - m.state[user.Id.String()] = v - } else { - a := map[string]ocm.ShareState{ - rs.Share.Id.String(): f.GetState(), - } - m.state[user.Id.String()] = a - } - - return rs, nil -} diff --git a/pkg/ocm/share/manager/memory/memory_test.go b/pkg/ocm/share/manager/memory/memory_test.go deleted file mode 100644 index ce54095b09..0000000000 --- a/pkg/ocm/share/manager/memory/memory_test.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package memory - -import ( - "sync" - "testing" - "time" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" -) - -func Test_mgr_SharesWorkflow(t *testing.T) { - - managerWithData := createManagerWithData() - user := userpb.User{ - Id: &userpb.UserId{ - Idp: "http://localhost:20080", - OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", - }, - Username: "", - Mail: "", - MailVerified: false, - DisplayName: "", - Groups: nil, - Opaque: nil, - } - // ctx:=context.Background() - - var filters []*ocm.ListOCMSharesRequest_Filter - share, err := managerWithData.listShares(&user, filters) - if err != nil { - t.Error(err) - } - if len(share) != 3 { - t.Errorf("ListShares invalid list size got = %v, want %v", len(share), 3) - } -} - -func createManagerWithData() *mgr { - now := time.Now().UnixNano() - ts := &typespb.Timestamp{ - Seconds: uint64(now / 1000000000), - Nanos: uint32(now % 1000000000), - } - user := userpb.User{ - Id: &userpb.UserId{ - Idp: "http://localhost:20080", - OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", - }, - } - g := &ocm.ShareGrant{ - Grantee: nil, - Permissions: &ocm.SharePermissions{}, - } - - s := &ocm.Share{ - Id: &ocm.ShareId{ - OpaqueId: "e45c5646-d202-4369-a21e-afe86985ea2a", - }, - ResourceId: &provider.ResourceId{ - StorageId: "123e4567-e89b-12d3-a456-426655440000", - OpaqueId: "fileid-einstein/a.txt", - }, - Permissions: g.Permissions, - Grantee: g.Grantee, - Owner: user.Id, - Creator: user.Id, - Ctime: ts, - Mtime: ts, - } - - s2 := &ocm.Share{ - Id: &ocm.ShareId{ - OpaqueId: "1a28c96e-34b2-480c-b14b-8799b3f411f6", - }, - ResourceId: &provider.ResourceId{ - StorageId: "123e4567-e89b-12d3-a456-426655440000", - OpaqueId: "fileid-einstein/b.txt", - }, - Permissions: g.Permissions, - Grantee: g.Grantee, - Owner: user.Id, - Creator: user.Id, - Ctime: ts, - Mtime: ts, - } - - s3 := &ocm.Share{ - Id: &ocm.ShareId{ - OpaqueId: "dd2ceead-852b-4d7c-8c89-73470c2a05ba", - }, - ResourceId: &provider.ResourceId{ - StorageId: "123e4567-e89b-12d3-a456-426655440000", - OpaqueId: "fileid-einstein/c.txt", - }, - Permissions: g.Permissions, - Grantee: g.Grantee, - Owner: user.Id, - Creator: user.Id, - Ctime: ts, - Mtime: ts, - } - - m := &mgr{ - shares: sync.Map{}, - state: nil, - } - - storeShare(m, s) - storeShare(m, s2) - storeShare(m, s3) - - return m -} - -func storeShare(m *mgr, s *ocm.Share) { - - key := &ocm.ShareKey{ - Owner: s.Owner, - ResourceId: s.ResourceId, - Grantee: s.Grantee, - } - - m.shares.Store(key, s) -} diff --git a/pkg/share/manager/json/json.go b/pkg/share/manager/json/json.go index 856d8c9281..b146428c2e 100644 --- a/pkg/share/manager/json/json.go +++ b/pkg/share/manager/json/json.go @@ -23,10 +23,11 @@ import ( "encoding/json" "io/ioutil" "os" - "reflect" "sync" "time" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" @@ -38,6 +39,7 @@ import ( "github.com/cs3org/reva/pkg/share/manager/registry" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" ) func init() { @@ -95,6 +97,18 @@ func loadOrCreate(file string) (*shareModel, error) { return nil, err } + // There are discrepancies in the marshalling of oneof fields, so these need + // to be stored separately + for i := range m.Grantees { + id := m.Grantees[i].(map[string]interface{}) + if m.Shares[i].Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { + m.Shares[i].Grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: id["opaque_id"].(string), Idp: id["idp"].(string)}} + } else if m.Shares[i].Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + m.Shares[i].Grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: id["opaque_id"].(string), Idp: id["idp"].(string)}} + } + } + m.Grantees = nil + if m.State == nil { m.State = map[string]map[string]collaboration.ShareState{} } @@ -104,13 +118,28 @@ func loadOrCreate(file string) (*shareModel, error) { } type shareModel struct { - file string - State map[string]map[string]collaboration.ShareState `json:"state"` // map[username]map[share_id]boolean - Shares []*collaboration.Share `json:"shares"` + file string + State map[string]map[string]collaboration.ShareState `json:"state"` // map[username]map[share_id]boolean + Shares []*collaboration.Share `json:"shares"` + Grantees []interface{} `json:"grantees"` } func (m *shareModel) Save() error { - data, err := json.Marshal(m) + temp := *m + temp.Grantees = []interface{}{} + temp.Shares = []*collaboration.Share{} + for i := range m.Shares { + s := *m.Shares[i] + if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { + temp.Grantees = append(temp.Grantees, s.Grantee.GetUserId()) + } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + temp.Grantees = append(temp.Grantees, s.Grantee.GetGroupId()) + } + s.Grantee = &provider.Grantee{Type: s.Grantee.Type} + temp.Shares = append(temp.Shares, &s) + } + + data, err := json.Marshal(temp) if err != nil { err = errors.Wrap(err, "error encoding to json") return err @@ -164,8 +193,7 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora // do not allow share to myself or the owner if share is for a user // TODO(labkode): should not this be caught already at the gw level? if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && - ((g.Grantee.Id.Idp == user.Id.Idp && g.Grantee.Id.OpaqueId == user.Id.OpaqueId) || - (g.Grantee.Id.Idp == md.Owner.Idp && g.Grantee.Id.OpaqueId == md.Owner.OpaqueId)) { + (utils.UserEqual(g.Grantee.GetUserId(), user.Id) || utils.UserEqual(g.Grantee.GetUserId(), md.Owner)) { return nil, errors.New("json: owner/creator and grantee are the same") } @@ -222,10 +250,8 @@ func (m *mgr) getByKey(ctx context.Context, key *collaboration.ShareKey) (*colla m.Lock() defer m.Unlock() for _, s := range m.model.Shares { - if ((key.Owner.Idp == s.Owner.Idp && key.Owner.OpaqueId == s.Owner.OpaqueId) || - (key.Owner.Idp == s.Creator.Idp && key.Owner.OpaqueId == s.Creator.OpaqueId)) && - key.ResourceId.StorageId == s.ResourceId.StorageId && key.ResourceId.OpaqueId == s.ResourceId.OpaqueId && - key.Grantee.Type == s.Grantee.Type && key.Grantee.Id.Idp == s.Grantee.Id.Idp && key.Grantee.Id.OpaqueId == s.Grantee.Id.OpaqueId { + if (utils.UserEqual(key.Owner, s.Owner) || utils.UserEqual(key.Owner, s.Creator)) && + utils.ResourceEqual(key.ResourceId, s.ResourceId) && utils.GranteeEqual(key.Grantee, s.Grantee) { return s, nil } } @@ -248,8 +274,7 @@ func (m *mgr) get(ctx context.Context, ref *collaboration.ShareReference) (s *co // check if we are the owner user := user.ContextMustGetUser(ctx) - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { return s, nil } @@ -271,9 +296,8 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er defer m.Unlock() user := user.ContextMustGetUser(ctx) for i, s := range m.model.Shares { - if equal(ref, s) { - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if sharesEqual(ref, s) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { m.model.Shares[len(m.model.Shares)-1], m.model.Shares[i] = m.model.Shares[i], m.model.Shares[len(m.model.Shares)-1] m.model.Shares = m.model.Shares[:len(m.model.Shares)-1] if err := m.model.Save(); err != nil { @@ -287,15 +311,14 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er return errtypes.NotFound(ref.String()) } -// TODO(labkode): this is fragile, the check should be done on primitive types. -func equal(ref *collaboration.ShareReference, s *collaboration.Share) bool { +func sharesEqual(ref *collaboration.ShareReference, s *collaboration.Share) bool { if ref.GetId() != nil && s.Id != nil { if ref.GetId().OpaqueId == s.Id.OpaqueId { return true } } else if ref.GetKey() != nil { - if (reflect.DeepEqual(*ref.GetKey().Owner, *s.Owner) || reflect.DeepEqual(*ref.GetKey().Owner, *s.Creator)) && - reflect.DeepEqual(*ref.GetKey().ResourceId, *s.ResourceId) && reflect.DeepEqual(*ref.GetKey().Grantee, *s.Grantee) { + if (utils.UserEqual(ref.GetKey().Owner, s.Owner) || utils.UserEqual(ref.GetKey().Owner, s.Creator)) && + utils.ResourceEqual(ref.GetKey().ResourceId, s.ResourceId) && utils.GranteeEqual(ref.GetKey().Grantee, s.Grantee) { return true } } @@ -307,9 +330,8 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference defer m.Unlock() user := user.ContextMustGetUser(ctx) for i, s := range m.model.Shares { - if equal(ref, s) { - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if sharesEqual(ref, s) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { now := time.Now().UnixNano() m.model.Shares[i].Permissions = p m.model.Shares[i].Mtime = &typespb.Timestamp{ @@ -333,9 +355,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListShare defer m.Unlock() user := user.ContextMustGetUser(ctx) for _, s := range m.model.Shares { - // TODO(labkode): add check for creator. - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { // no filter we return earlier if len(filters) == 0 { ss = append(ss, s) @@ -344,7 +364,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListShare // TODO(labkode): add the rest of filters. for _, f := range filters { if f.Type == collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID { - if s.ResourceId.StorageId == f.GetResourceId().StorageId && s.ResourceId.OpaqueId == f.GetResourceId().OpaqueId { + if utils.ResourceEqual(s.ResourceId, f.GetResourceId()) { ss = append(ss, s) } } @@ -362,20 +382,17 @@ func (m *mgr) ListReceivedShares(ctx context.Context) ([]*collaboration.Received defer m.Unlock() user := user.ContextMustGetUser(ctx) for _, s := range m.model.Shares { - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { // omit shares created by me continue } - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { - if user.Id.Idp == s.Grantee.Id.Idp && user.Id.OpaqueId == s.Grantee.Id.OpaqueId { - rs := m.convert(ctx, s) - rss = append(rss, rs) - } + if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { + rs := m.convert(ctx, s) + rss = append(rss, rs) } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { // check if all user groups match this share; TODO(labkode): filter shares created by us. for _, g := range user.Groups { - if g == s.Grantee.Id.OpaqueId { + if g == s.Grantee.GetGroupId().OpaqueId { rs := m.convert(ctx, s) rss = append(rss, rs) } @@ -409,14 +426,13 @@ func (m *mgr) getReceived(ctx context.Context, ref *collaboration.ShareReference defer m.Unlock() user := user.ContextMustGetUser(ctx) for _, s := range m.model.Shares { - if equal(ref, s) { - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && - s.Grantee.Id.Idp == user.Id.Idp && s.Grantee.Id.OpaqueId == user.Id.OpaqueId { + if sharesEqual(ref, s) { + if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { rs := m.convert(ctx, s) return rs, nil } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { for _, g := range user.Groups { - if s.Grantee.Id.OpaqueId == g { + if s.Grantee.GetGroupId().OpaqueId == g { rs := m.convert(ctx, s) return rs, nil } diff --git a/pkg/share/manager/memory/memory.go b/pkg/share/manager/memory/memory.go index a811e173aa..e3d6cda8bb 100644 --- a/pkg/share/manager/memory/memory.go +++ b/pkg/share/manager/memory/memory.go @@ -22,7 +22,6 @@ import ( "context" "errors" "fmt" - "reflect" "sync" "sync/atomic" "time" @@ -35,6 +34,7 @@ import ( "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/share/manager/registry" "github.com/cs3org/reva/pkg/user" + "github.com/cs3org/reva/pkg/utils" ) var counter uint64 @@ -75,11 +75,9 @@ func (m *manager) Share(ctx context.Context, md *provider.ResourceInfo, g *colla Nanos: uint32(now % 1000000000), } - // do not allow share to myself or the owner if share is for a user if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && - ((g.Grantee.Id.Idp == user.Id.Idp && g.Grantee.Id.OpaqueId == user.Id.OpaqueId) || - (g.Grantee.Id.Idp == md.Owner.Idp && g.Grantee.Id.OpaqueId == md.Owner.OpaqueId)) { - return nil, errors.New("json: owner/creator and grantee are the same") + (utils.UserEqual(g.Grantee.GetUserId(), user.Id) || utils.UserEqual(g.Grantee.GetUserId(), md.Owner)) { + return nil, errors.New("memory: owner/creator and grantee are the same") } // check if share already exists. @@ -126,10 +124,8 @@ func (m *manager) getByKey(ctx context.Context, key *collaboration.ShareKey) (*c m.lock.Lock() defer m.lock.Unlock() for _, s := range m.shares { - if ((key.Owner.Idp == s.Owner.Idp && key.Owner.OpaqueId == s.Owner.OpaqueId) || - (key.Owner.Idp == s.Creator.Idp && key.Owner.OpaqueId == s.Creator.OpaqueId)) && - key.ResourceId.StorageId == s.ResourceId.StorageId && key.ResourceId.OpaqueId == s.ResourceId.OpaqueId && - key.Grantee.Type == s.Grantee.Type && key.Grantee.Id.Idp == s.Grantee.Id.Idp && key.Grantee.Id.OpaqueId == s.Grantee.Id.OpaqueId { + if (utils.UserEqual(key.Owner, s.Owner) || utils.UserEqual(key.Owner, s.Creator)) && + utils.ResourceEqual(key.ResourceId, s.ResourceId) && utils.GranteeEqual(key.Grantee, s.Grantee) { return s, nil } } @@ -152,8 +148,7 @@ func (m *manager) get(ctx context.Context, ref *collaboration.ShareReference) (s // check if we are the owner user := user.ContextMustGetUser(ctx) - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { return s, nil } @@ -175,9 +170,8 @@ func (m *manager) Unshare(ctx context.Context, ref *collaboration.ShareReference defer m.lock.Unlock() user := user.ContextMustGetUser(ctx) for i, s := range m.shares { - if equal(ref, s) { - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if sharesEqual(ref, s) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { m.shares[len(m.shares)-1], m.shares[i] = m.shares[i], m.shares[len(m.shares)-1] m.shares = m.shares[:len(m.shares)-1] return nil @@ -187,15 +181,14 @@ func (m *manager) Unshare(ctx context.Context, ref *collaboration.ShareReference return errtypes.NotFound(ref.String()) } -// TODO(labkode): this is fragile, the check should be done on primitive types. -func equal(ref *collaboration.ShareReference, s *collaboration.Share) bool { +func sharesEqual(ref *collaboration.ShareReference, s *collaboration.Share) bool { if ref.GetId() != nil && s.Id != nil { if ref.GetId().OpaqueId == s.Id.OpaqueId { return true } } else if ref.GetKey() != nil { - if (reflect.DeepEqual(*ref.GetKey().Owner, *s.Owner) || reflect.DeepEqual(*ref.GetKey().Owner, *s.Creator)) && - reflect.DeepEqual(*ref.GetKey().ResourceId, *s.ResourceId) && reflect.DeepEqual(*ref.GetKey().Grantee, *s.Grantee) { + if (utils.UserEqual(ref.GetKey().Owner, s.Owner) || utils.UserEqual(ref.GetKey().Owner, s.Creator)) && + utils.ResourceEqual(ref.GetKey().ResourceId, s.ResourceId) && utils.GranteeEqual(ref.GetKey().Grantee, s.Grantee) { return true } } @@ -207,9 +200,8 @@ func (m *manager) UpdateShare(ctx context.Context, ref *collaboration.ShareRefer defer m.lock.Unlock() user := user.ContextMustGetUser(ctx) for i, s := range m.shares { - if equal(ref, s) { - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if sharesEqual(ref, s) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { now := time.Now().UnixNano() m.shares[i].Permissions = p m.shares[i].Mtime = &typespb.Timestamp{ @@ -229,8 +221,7 @@ func (m *manager) ListShares(ctx context.Context, filters []*collaboration.ListS defer m.lock.Unlock() user := user.ContextMustGetUser(ctx) for _, s := range m.shares { - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { // no filter we return earlier if len(filters) == 0 { ss = append(ss, s) @@ -239,7 +230,7 @@ func (m *manager) ListShares(ctx context.Context, filters []*collaboration.ListS // TODO(labkode): add the rest of filters. for _, f := range filters { if f.Type == collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID { - if s.ResourceId.StorageId == f.GetResourceId().StorageId && s.ResourceId.OpaqueId == f.GetResourceId().OpaqueId { + if utils.ResourceEqual(s.ResourceId, f.GetResourceId()) { ss = append(ss, s) } } @@ -257,20 +248,17 @@ func (m *manager) ListReceivedShares(ctx context.Context) ([]*collaboration.Rece defer m.lock.Unlock() user := user.ContextMustGetUser(ctx) for _, s := range m.shares { - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { + if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { // omit shares created by me continue } - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { - if user.Id.Idp == s.Grantee.Id.Idp && user.Id.OpaqueId == s.Grantee.Id.OpaqueId { - rs := m.convert(ctx, s) - rss = append(rss, rs) - } + if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { + rs := m.convert(ctx, s) + rss = append(rss, rs) } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { // check if all user groups match this share; TODO(labkode): filter shares created by us. for _, g := range user.Groups { - if g == s.Grantee.Id.OpaqueId { + if g == s.Grantee.GetGroupId().OpaqueId { rs := m.convert(ctx, s) rss = append(rss, rs) } @@ -304,14 +292,13 @@ func (m *manager) getReceived(ctx context.Context, ref *collaboration.ShareRefer defer m.lock.Unlock() user := user.ContextMustGetUser(ctx) for _, s := range m.shares { - if equal(ref, s) { - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && - s.Grantee.Id.Idp == user.Id.Idp && s.Grantee.Id.OpaqueId == user.Id.OpaqueId { + if sharesEqual(ref, s) { + if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { rs := m.convert(ctx, s) return rs, nil } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { for _, g := range user.Groups { - if s.Grantee.Id.OpaqueId == g { + if s.Grantee.GetGroupId().OpaqueId == g { rs := m.convert(ctx, s) return rs, nil } diff --git a/pkg/storage/fs/ocis/grants.go b/pkg/storage/fs/ocis/grants.go index db9d6f8e15..329a471165 100644 --- a/pkg/storage/fs/ocis/grants.go +++ b/pkg/storage/fs/ocis/grants.go @@ -124,9 +124,9 @@ func (fs *ocisfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *p var attr string if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - attr = grantPrefix + _groupAcePrefix + g.Grantee.Id.OpaqueId + attr = grantPrefix + _groupAcePrefix + g.Grantee.GetGroupId().OpaqueId } else { - attr = grantPrefix + _userAcePrefix + g.Grantee.Id.OpaqueId + attr = grantPrefix + _userAcePrefix + g.Grantee.GetUserId().OpaqueId } np := fs.lu.toInternalPath(node.ID) diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index feb12b596a..7625b0ca77 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -1064,9 +1064,9 @@ func (fs *ocfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pro var attr string if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - attr = sharePrefix + "g:" + g.Grantee.Id.OpaqueId + attr = sharePrefix + "g:" + g.Grantee.GetGroupId().OpaqueId } else { - attr = sharePrefix + "u:" + g.Grantee.Id.OpaqueId + attr = sharePrefix + "u:" + g.Grantee.GetUserId().OpaqueId } if err = xattr.Remove(ip, attr); err != nil { diff --git a/pkg/storage/fs/s3ng/grants.go b/pkg/storage/fs/s3ng/grants.go index fce9d680e0..f669d8b211 100644 --- a/pkg/storage/fs/s3ng/grants.go +++ b/pkg/storage/fs/s3ng/grants.go @@ -126,9 +126,9 @@ func (fs *s3ngfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *p var attr string if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - attr = xattrs.GrantPrefix + xattrs.GroupAcePrefix + g.Grantee.Id.OpaqueId + attr = xattrs.GrantPrefix + xattrs.GroupAcePrefix + g.Grantee.GetGroupId().OpaqueId } else { - attr = xattrs.GrantPrefix + xattrs.UserAcePrefix + g.Grantee.Id.OpaqueId + attr = xattrs.GrantPrefix + xattrs.UserAcePrefix + g.Grantee.GetUserId().OpaqueId } np := fs.lu.InternalPath(node.ID) diff --git a/pkg/storage/migrate/shares.go b/pkg/storage/migrate/shares.go index 64cbb3a6a0..8bd1943dc6 100644 --- a/pkg/storage/migrate/shares.go +++ b/pkg/storage/migrate/shares.go @@ -97,9 +97,9 @@ func shareReq(info *provider.ResourceInfo, share *share) *collaboration.CreateSh Grant: &collaboration.ShareGrant{ Grantee: &provider.Grantee{ Type: provider.GranteeType_GRANTEE_TYPE_USER, - Id: &user.UserId{ + Id: &provider.Grantee_UserId{UserId: &user.UserId{ OpaqueId: share.SharedWith, - }, + }}, }, Permissions: &collaboration.SharePermissions{ Permissions: convertPermissions(share.Permissions), diff --git a/pkg/storage/utils/ace/ace.go b/pkg/storage/utils/ace/ace.go index 4098ce94c0..1057abb082 100644 --- a/pkg/storage/utils/ace/ace.go +++ b/pkg/storage/utils/ace/ace.go @@ -24,6 +24,7 @@ import ( "strconv" "strings" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ) @@ -132,9 +133,9 @@ func FromGrant(g *provider.Grant) *ACE { } if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { e.flags = "g" - e.principal = "g:" + g.Grantee.Id.OpaqueId + e.principal = "g:" + g.Grantee.GetGroupId().OpaqueId } else { - e.principal = "u:" + g.Grantee.Id.OpaqueId + e.principal = "u:" + g.Grantee.GetUserId().OpaqueId } return e } @@ -180,13 +181,18 @@ func Unmarshal(principal string, v []byte) (e *ACE, err error) { // Grant returns a CS3 grant func (e *ACE) Grant() *provider.Grant { - return &provider.Grant{ + g := &provider.Grant{ Grantee: &provider.Grantee{ - Id: &userpb.UserId{OpaqueId: e.principal}, Type: e.granteeType(), }, Permissions: e.grantPermissionSet(), } + if e.granteeType() == provider.GranteeType_GRANTEE_TYPE_GROUP { + g.Grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: e.principal}} + } else if e.granteeType() == provider.GranteeType_GRANTEE_TYPE_USER { + g.Grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: e.principal}} + } + return g } // granteeType returns the CS3 grantee type diff --git a/pkg/storage/utils/acl/acl.go b/pkg/storage/utils/acl/acl.go index ed0459e5de..a8f3327331 100644 --- a/pkg/storage/utils/acl/acl.go +++ b/pkg/storage/utils/acl/acl.go @@ -43,7 +43,7 @@ const ( // TypeUser indicates the qualifier identifies a user TypeUser = "u" // TypeGroup indicates the qualifier identifies a group - TypeGroup = "g" + TypeGroup = "egroup" ) // Parse parses an acl string with the given delimiter (LongTextForm or ShortTextForm) @@ -84,7 +84,6 @@ func (m *ACLs) Serialize() string { // DeleteEntry removes an entry uniquely identified by acl type and qualifier func (m *ACLs) DeleteEntry(aclType string, qualifier string) { - aclType = getShortType(aclType) for i, e := range m.Entries { if e.Qualifier == qualifier && e.Type == aclType { m.Entries = append(m.Entries[:i], m.Entries[i+1:]...) @@ -100,7 +99,7 @@ func (m *ACLs) SetEntry(aclType string, qualifier string, permissions string) er } m.DeleteEntry(aclType, qualifier) entry := &Entry{ - Type: getShortType(aclType), + Type: aclType, Qualifier: qualifier, Permissions: permissions, } @@ -126,7 +125,7 @@ func ParseEntry(singleSysACL string) (*Entry, error) { } return &Entry{ - Type: getShortType(tokens[0]), + Type: tokens[0], Qualifier: tokens[1], Permissions: tokens[2], }, nil @@ -137,17 +136,6 @@ func (a *Entry) CitrineSerialize() string { return fmt.Sprintf("%s:%s=%s", a.Type, a.Qualifier, a.Permissions) } -func getShortType(aclType string) string { - switch aclType[:1] { - case TypeUser: - return TypeUser - case TypeGroup: - return TypeGroup - default: - return aclType // TODO mark as invalid? - } -} - func (a *Entry) serialize() string { return strings.Join([]string{a.Type, a.Qualifier, a.Permissions}, ":") } diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index b441ab5a04..01574982c8 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -31,6 +31,7 @@ import ( "strings" "sync" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -405,15 +406,17 @@ func (fs *eosfs) getEosACL(ctx context.Context, g *provider.Grant) (*acl.Entry, if err != nil { return nil, err } - qualifier := g.Grantee.Id.OpaqueId - // since EOS Citrine ACLs are stored with uid, we need to convert username to - // uid only for users. + var qualifier string if t == acl.TypeUser { - qualifier, _, err = fs.getUIDGateway(ctx, g.Grantee.Id) + // since EOS Citrine ACLs are stored with uid, we need to convert username to + // uid only for users. + qualifier, _, err = fs.getUIDGateway(ctx, g.Grantee.GetUserId()) if err != nil { return nil, err } + } else { + qualifier = g.Grantee.GetGroupId().OpaqueId } eosACL := &acl.Entry{ @@ -434,14 +437,16 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr if err != nil { return err } - recipient := g.Grantee.Id.OpaqueId - // since EOS Citrine ACLs are stored with uid, we need to convert username to uid + var recipient string if eosACLType == acl.TypeUser { - recipient, _, err = fs.getUIDGateway(ctx, g.Grantee.Id) + // since EOS Citrine ACLs are stored with uid, we need to convert username to uid + recipient, _, err = fs.getUIDGateway(ctx, g.Grantee.GetUserId()) if err != nil { return err } + } else { + recipient = g.Grantee.GetGroupId().OpaqueId } eosACL := &acl.Entry{ @@ -501,22 +506,27 @@ func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*pr grantList := []*provider.Grant{} for _, a := range acls { - // EOS Citrine ACLs are stored with uid. - // This needs to be resolved to the user opaque ID. - qualifier := &userpb.UserId{OpaqueId: a.Qualifier} + var grantee *provider.Grantee if a.Type == acl.TypeUser { - qualifier, err = fs.getUserIDGateway(ctx, a.Qualifier) + // EOS Citrine ACLs are stored with uid for users. + // This needs to be resolved to the user opaque ID. + qualifier, err := fs.getUserIDGateway(ctx, a.Qualifier) if err != nil { return nil, err } - } - grantee := &provider.Grantee{ - Id: qualifier, - Type: grants.GetGranteeType(a.Type), + grantee = &provider.Grantee{ + Id: &provider.Grantee_UserId{UserId: qualifier}, + Type: grants.GetGranteeType(a.Type), + } + } else { + grantee = &provider.Grantee{ + Id: &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: a.Qualifier}}, + Type: grants.GetGranteeType(a.Type), + } } grantList = append(grantList, &provider.Grant{ Grantee: grantee, - Permissions: grants.GetGrantPermissionSet(a.Permissions), + Permissions: grants.GetGrantPermissionSet(a.Permissions, true), }) } @@ -783,30 +793,24 @@ func (fs *eosfs) createShadowHome(ctx context.Context) error { if err != nil { return errors.Wrap(err, "eos: no user in ctx") } - - home := fs.wrapShadow(ctx, "/") uid, gid, err := fs.getRootUIDAndGID(ctx) if err != nil { return nil } - _, err = fs.c.GetFileInfoByPath(ctx, uid, gid, home) - if err != nil { // home already exists - // TODO(labkode): abort on any error that is not found - if _, ok := err.(errtypes.IsNotFound); !ok { - return errors.Wrap(err, "eos: error verifying if user home directory exists") - } - - err = fs.createUserDir(ctx, u, home) - if err != nil { - return err - } - } - + home := fs.wrapShadow(ctx, "/") shadowFolders := []string{fs.conf.ShareFolder} + for _, sf := range shadowFolders { - err = fs.createUserDir(ctx, u, path.Join(home, sf)) + fn := path.Join(home, sf) + _, err = fs.c.GetFileInfoByPath(ctx, uid, gid, fn) if err != nil { - return err + if _, ok := err.(errtypes.IsNotFound); !ok { + return errors.Wrap(err, "eos: error verifying if shadow directory exists") + } + err = fs.createUserDir(ctx, u, fn, false) + if err != nil { + return err + } } } @@ -829,12 +833,11 @@ func (fs *eosfs) createNominalHome(ctx context.Context) error { return nil } - // TODO(labkode): abort on any error that is not found if _, ok := err.(errtypes.IsNotFound); !ok { return errors.Wrap(err, "eos: error verifying if user home directory exists") } - err = fs.createUserDir(ctx, u, home) + err = fs.createUserDir(ctx, u, home, false) return err } @@ -854,7 +857,7 @@ func (fs *eosfs) CreateHome(ctx context.Context) error { return nil } -func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string) error { +func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, recursiveAttr bool) error { uid, gid, err := fs.getRootUIDAndGID(ctx) if err != nil { return nil @@ -905,7 +908,7 @@ func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string) } for _, attr := range attrs { - err = fs.c.SetAttr(ctx, uid, gid, attr, true, path) + err = fs.c.SetAttr(ctx, uid, gid, attr, recursiveAttr, path) if err != nil { return errors.Wrap(err, "eos: error setting attribute") } @@ -957,7 +960,7 @@ func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.U if err != nil { return nil } - if err := fs.createUserDir(ctx, u, tmp); err != nil { + if err := fs.createUserDir(ctx, u, tmp, false); err != nil { err = errors.Wrapf(err, "eos: error creating temporary ref file") return err } @@ -1354,54 +1357,13 @@ func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileI } var perm string - var rp provider.ResourcePermissions for _, e := range eosFileInfo.SysACL.Entries { if e.Qualifier == uid || e.Qualifier == gid { perm = e.Permissions } } - // Rules adapted from https://github.com/cern-eos/eos/blob/master/doc/configuration/permission.rst - if strings.Contains(perm, "r") && !strings.Contains(perm, "!r") { - rp.GetPath = true - rp.Stat = true - rp.ListFileVersions = true - rp.InitiateFileDownload = true - if eosFileInfo.IsDir { - rp.ListContainer = true - } - } - - if strings.Contains(perm, "w") && !strings.Contains(perm, "!w") { - rp.Move = true - rp.Delete = true - rp.InitiateFileUpload = true - rp.RestoreFileVersion = true - if eosFileInfo.IsDir { - rp.CreateContainer = true - } - } - - if strings.Contains(perm, "!x") { - rp.ListContainer = false - rp.ListFileVersions = false - } - - if strings.Contains(perm, "!d") { - rp.Delete = false - } - - if strings.Contains(perm, "m") && !strings.Contains(perm, "!m") { - rp.AddGrant = true - rp.ListGrants = true - rp.RemoveGrant = true - } - - if strings.Contains(perm, "q") && !strings.Contains(perm, "!q") { - rp.GetQuota = true - } - - return &rp + return grants.GetGrantPermissionSet(perm, eosFileInfo.IsDir) } func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) { diff --git a/pkg/storage/utils/grants/grants.go b/pkg/storage/utils/grants/grants.go index b3d6ca9ff5..edcfb321d3 100644 --- a/pkg/storage/utils/grants/grants.go +++ b/pkg/storage/utils/grants/grants.go @@ -23,6 +23,7 @@ import ( "strings" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/storage/utils/acl" ) // GetACLPerm generates a string representation of CS3APIs' ResourcePermissions @@ -36,9 +37,15 @@ func GetACLPerm(set *provider.ResourcePermissions) (string, error) { if set.CreateContainer || set.InitiateFileUpload || set.Delete || set.Move { b.WriteString("w") } - if set.ListContainer { + if set.ListContainer || set.ListFileVersions { b.WriteString("x") } + if set.AddGrant || set.ListGrants || set.RemoveGrant { + b.WriteString("m") + } + if set.GetQuota { + b.WriteString("q") + } if set.Delete { b.WriteString("+d") @@ -46,79 +53,63 @@ func GetACLPerm(set *provider.ResourcePermissions) (string, error) { b.WriteString("!d") } - // TODO sharing - // TODO trash - // TODO versions return b.String(), nil } -// GetGrantPermissionSet converts CSEAPIs' ResourcePermissions from a string +// GetGrantPermissionSet converts CS3APIs' ResourcePermissions from a string // TODO(labkode): add more fine grained controls. // EOS acls are a mix of ACLs and POSIX permissions. More details can be found in // https://github.com/cern-eos/eos/blob/master/doc/configuration/permission.rst -// TODO we need to evaluate all acls in the list at once to properly forbid (!) and overwrite (+) permissions -// This is ugly, because those are actually negative permissions ... -func GetGrantPermissionSet(mode string) *provider.ResourcePermissions { - - // TODO also check unix permissions for read access - p := &provider.ResourcePermissions{} - // r - if strings.Contains(mode, "r") { - p.Stat = true - p.InitiateFileDownload = true +func GetGrantPermissionSet(perm string, isDir bool) *provider.ResourcePermissions { + var rp provider.ResourcePermissions + + if strings.Contains(perm, "r") && !strings.Contains(perm, "!r") { + rp.GetPath = true + rp.Stat = true + rp.InitiateFileDownload = true + } + + if strings.Contains(perm, "w") && !strings.Contains(perm, "!w") { + rp.Move = true + rp.Delete = true + rp.InitiateFileUpload = true + rp.RestoreFileVersion = true + if isDir { + rp.CreateContainer = true + } } - // w - if strings.Contains(mode, "w") { - p.CreateContainer = true - p.InitiateFileUpload = true - p.Delete = true - if p.InitiateFileDownload { - p.Move = true + + if strings.Contains(perm, "x") && !strings.Contains(perm, "!x") { + rp.ListFileVersions = true + if isDir { + rp.ListContainer = true } } - if strings.Contains(mode, "wo") { - p.CreateContainer = true - // p.InitiateFileUpload = false // TODO only when the file exists - p.Delete = false + + if strings.Contains(perm, "!d") { + rp.Delete = false } - if strings.Contains(mode, "!d") { - p.Delete = false - } else if strings.Contains(mode, "+d") { - p.Delete = true + + if strings.Contains(perm, "m") && !strings.Contains(perm, "!m") { + rp.AddGrant = true + rp.ListGrants = true + rp.RemoveGrant = true } - // x - if strings.Contains(mode, "x") { - p.ListContainer = true + + if strings.Contains(perm, "q") && !strings.Contains(perm, "!q") { + rp.GetQuota = true } - // sharing - // TODO AddGrant - // TODO ListGrants - // TODO RemoveGrant - // TODO UpdateGrant - - // trash - // TODO ListRecycle - // TODO RestoreRecycleItem - // TODO PurgeRecycle - - // versions - // TODO ListFileVersions - // TODO RestoreFileVersion - - // ? - // TODO GetPath - // TODO GetQuota - return p + return &rp } // GetACLType returns a char representation of the type of grantee func GetACLType(gt provider.GranteeType) (string, error) { switch gt { case provider.GranteeType_GRANTEE_TYPE_USER: - return "u", nil + return acl.TypeUser, nil case provider.GranteeType_GRANTEE_TYPE_GROUP: - return "g", nil + return acl.TypeGroup, nil default: return "", errors.New("no eos acl for grantee type: " + gt.String()) } @@ -127,9 +118,9 @@ func GetACLType(gt provider.GranteeType) (string, error) { // GetGranteeType returns the grantee type from a char func GetGranteeType(aclType string) provider.GranteeType { switch aclType { - case "u": + case acl.TypeUser: return provider.GranteeType_GRANTEE_TYPE_USER - case "g": + case acl.TypeGroup: return provider.GranteeType_GRANTEE_TYPE_GROUP default: return provider.GranteeType_GRANTEE_TYPE_INVALID diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index ff03a362ef..6ee2b6a0c6 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -31,12 +31,14 @@ import ( "strings" "time" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/mime" "github.com/cs3org/reva/pkg/storage" + "github.com/cs3org/reva/pkg/storage/utils/acl" "github.com/cs3org/reva/pkg/storage/utils/chunking" "github.com/cs3org/reva/pkg/storage/utils/grants" "github.com/cs3org/reva/pkg/storage/utils/templates" @@ -432,7 +434,12 @@ func (fs *localfs) AddGrant(ctx context.Context, ref *provider.Reference, g *pro if err != nil { return errors.Wrap(err, "localfs: error getting grantee type") } - grantee := fmt.Sprintf("%s:%s@%s", granteeType, g.Grantee.Id.OpaqueId, g.Grantee.Id.Idp) + var grantee string + if granteeType == acl.TypeUser { + grantee = fmt.Sprintf("%s:%s@%s", granteeType, g.Grantee.GetUserId().OpaqueId, g.Grantee.GetUserId().Idp) + } else if granteeType == acl.TypeGroup { + grantee = fmt.Sprintf("%s:%s@%s", granteeType, g.Grantee.GetGroupId().OpaqueId, g.Grantee.GetGroupId().Idp) + } err = fs.addToACLDB(ctx, fn, grantee, role) if err != nil { @@ -461,11 +468,15 @@ func (fs *localfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]* if err != nil { return nil, errors.Wrap(err, "localfs: error scanning db rows") } - grantee := &provider.Grantee{ - Id: &userpb.UserId{OpaqueId: granteeID[2:]}, - Type: grants.GetGranteeType(string(granteeID[0])), + grantSplit := strings.Split(granteeID, ":") + grantee := &provider.Grantee{Type: grants.GetGranteeType(grantSplit[0])} + parts := strings.Split(grantSplit[1], "@") + if grantSplit[0] == acl.TypeUser { + grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: parts[0], Idp: parts[1]}} + } else if grantSplit[0] == acl.TypeGroup { + grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: parts[0], Idp: parts[1]}} } - permissions := grants.GetGrantPermissionSet(role) + permissions := grants.GetGrantPermissionSet(role, true) grantList = append(grantList, &provider.Grant{ Grantee: grantee, @@ -487,7 +498,12 @@ func (fs *localfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g * if err != nil { return errors.Wrap(err, "localfs: error getting grantee type") } - grantee := fmt.Sprintf("%s:%s@%s", granteeType, g.Grantee.Id.OpaqueId, g.Grantee.Id.Idp) + var grantee string + if granteeType == acl.TypeUser { + grantee = fmt.Sprintf("%s:%s@%s", granteeType, g.Grantee.GetUserId().OpaqueId, g.Grantee.GetUserId().Idp) + } else if granteeType == acl.TypeGroup { + grantee = fmt.Sprintf("%s:%s@%s", granteeType, g.Grantee.GetGroupId().OpaqueId, g.Grantee.GetGroupId().Idp) + } err = fs.removeFromACLDB(ctx, fn, grantee) if err != nil { diff --git a/pkg/ttlmap/ttlmap.go b/pkg/ttlmap/ttlmap.go deleted file mode 100644 index 30fa17a0ea..0000000000 --- a/pkg/ttlmap/ttlmap.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package ttlmap - -import ( - "sync" - "time" -) - -// TTLMap is a simple kv cache, based on https://stackoverflow.com/a/25487392 -// The ttl of an item will be reset whenever it is read or written. -type TTLMap struct { - m map[string]*item - l sync.Mutex -} - -type item struct { - value interface{} - lastAccess int64 -} - -// New creates a new ttl cache, preallocating space for ln items and the given maxttl -func New(ln int, maxTTL int) (m *TTLMap) { - m = &TTLMap{m: make(map[string]*item, ln)} - go func() { - for now := range time.Tick(time.Second) { - m.l.Lock() - for k, v := range m.m { - if now.Unix()-v.lastAccess > int64(maxTTL) { - delete(m.m, k) - } - } - m.l.Unlock() - } - }() - return -} - -// Len returns the current number of items in the cache -func (m *TTLMap) Len() int { - return len(m.m) -} - -// Put sets or overwrites an item, resetting the ttl -func (m *TTLMap) Put(k string, v interface{}) { - m.l.Lock() - it, ok := m.m[k] - if !ok { - it = &item{value: v} - m.m[k] = it - } - it.lastAccess = time.Now().Unix() - m.l.Unlock() -} - -// Get retrieves an item from the cache, resetting the ttl -func (m *TTLMap) Get(k string) (v interface{}) { - m.l.Lock() - if it, ok := m.m[k]; ok { - v = it.value - it.lastAccess = time.Now().Unix() - } - m.l.Unlock() - return - -} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 0b6c989a8c..ad2a9cbcd8 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -27,6 +27,9 @@ import ( "strings" "time" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) @@ -106,3 +109,40 @@ func TSToUnixNano(ts *types.Timestamp) uint64 { func TSToTime(ts *types.Timestamp) time.Time { return time.Unix(int64(ts.Seconds), int64(ts.Nanos)) } + +// ExtractGranteeID returns the ID, user or group, set in the GranteeId object +func ExtractGranteeID(grantee *provider.Grantee) (*userpb.UserId, *grouppb.GroupId) { + switch t := grantee.Id.(type) { + case *provider.Grantee_UserId: + return t.UserId, nil + case *provider.Grantee_GroupId: + return nil, t.GroupId + default: + return nil, nil + } +} + +// UserEqual returns whether two users have the same field values. +func UserEqual(u, v *userpb.UserId) bool { + return u != nil && v != nil && u.Idp == v.Idp && u.OpaqueId == v.OpaqueId +} + +// GroupEqual returns whether two groups have the same field values. +func GroupEqual(u, v *grouppb.GroupId) bool { + return u != nil && v != nil && u.Idp == v.Idp && u.OpaqueId == v.OpaqueId +} + +// ResourceEqual returns whether two resources have the same field values. +func ResourceEqual(u, v *provider.ResourceId) bool { + return u != nil && v != nil && u.StorageId == v.StorageId && u.OpaqueId == v.OpaqueId +} + +// GranteeEqual returns whether two grantees have the same field values. +func GranteeEqual(u, v *provider.Grantee) bool { + if u == nil || v == nil { + return false + } + uu, ug := ExtractGranteeID(u) + vu, vg := ExtractGranteeID(v) + return u.Type == v.Type && (UserEqual(uu, vu) || GroupEqual(ug, vg)) +} diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 25fbebeaed..944909ebd1 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -1627,12 +1627,6 @@ Scenario Outline: Renaming a file to a path with extension .part should not be p - [apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature#L15) - [apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature#L18) - [apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature#L21) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L51) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L52) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L70) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L71) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L72) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L73) - [apiShareManagementToShares/moveReceivedShare.feature:14](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveReceivedShare.feature#L14) - [apiShareManagementToShares/moveReceivedShare.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveReceivedShare.feature#L28) - [apiShareManagementToShares/moveReceivedShare.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveReceivedShare.feature#L39) @@ -2027,7 +2021,7 @@ API, search, favorites, config, capabilities, not existing endpoints, CORS and o #### [HTTP 401 Unauthorized responses don't contain a body](https://github.com/owncloud/ocis/issues/1337) - [apiAuthOcs/ocsDELETEAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsDELETEAuth.feature#L10) Scenario: send DELETE requests to OCS endpoints as admin with wrong password -- [apiAuthOcs/ocsGETAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L10) Scenario: using OCS anonymously +- [apiAuthOcs/ocsGETAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L10) Scenario: using OCS anonymously - [apiAuthOcs/ocsGETAuth.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L33) Scenario: ocs config end point accessible by unauthorized users - [apiAuthOcs/ocsGETAuth.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L53) Scenario: using OCS with non-admin basic auth - [apiAuthOcs/ocsGETAuth.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L88) Scenario: using OCS as normal user with wrong password @@ -2315,4 +2309,3 @@ Scenario Outline: Do a PROPFIND to a non-existing URL - [apiTranslation/translation.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L29) - [apiTranslation/translation.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L30) - diff --git a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.md b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.md index 6e33af41a8..034d028195 100644 --- a/tests/acceptance/expected-failures-on-OWNCLOUD-storage.md +++ b/tests/acceptance/expected-failures-on-OWNCLOUD-storage.md @@ -1747,12 +1747,6 @@ Scenario Outline: Moving a file into a shared folder as the sharee and as the sh - [apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature#L15) - [apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature#L18) - [apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature#L21) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L51) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L52) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L70) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L71) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L72) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L73) - [apiShareManagementToShares/moveReceivedShare.feature:14](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveReceivedShare.feature#L14) - [apiShareManagementToShares/moveReceivedShare.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveReceivedShare.feature#L28) - [apiShareManagementToShares/moveReceivedShare.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveReceivedShare.feature#L39) @@ -2448,4 +2442,3 @@ Scenario Outline: Do a PROPFIND to a non-existing URL - [apiTranslation/translation.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L29) - [apiTranslation/translation.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L30) - diff --git a/tests/acceptance/features/apiOcisSpecific/apiShareCreateSpecial2-createShareWithInvalidPermissions.feature b/tests/acceptance/features/apiOcisSpecific/apiShareCreateSpecial2-createShareWithInvalidPermissions.feature deleted file mode 100644 index 20dc66be49..0000000000 --- a/tests/acceptance/features/apiOcisSpecific/apiShareCreateSpecial2-createShareWithInvalidPermissions.feature +++ /dev/null @@ -1,45 +0,0 @@ -@api @files_sharing-app-required @issue-ocis-reva-243 -Feature: cannot share resources with invalid permissions - - Background: - Given user "Alice" has been created with default attributes and without skeleton files - And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" - And user "Alice" has created folder "/PARENT" - - @issue-ocis-reva-45 @issue-ocis-reva-243 @skipOnOcis-OCIS-Storage - # after fixing all issues delete this Scenario and use the one from oC10 core - Scenario Outline: Cannot create a share of a file with a user with only create permission - Given using OCS API version "" - And user "Brian" has been created with default attributes and without skeleton files - When user "Alice" creates a share using the sharing API with settings - | path | textfile0.txt | - | shareWith | Brian | - | shareType | user | - | permissions | create | - Then the OCS status code should be "" or "" - And the HTTP status code should be "" or "" - And as "Brian" entry "textfile0.txt" should not exist - Examples: - | ocs_api_version | ocs_status_code | eos_status_code | http_status_code_ocs | http_status_code_eos | - | 1 | 100 | 996 | 200 | 500 | - | 2 | 200 | 996 | 200 | 500 | - - @issue-ocis-reva-45 @issue-ocis-reva-243 @skipOnOcis-OCIS-Storage - # after fixing all issues delete this Scenario and use the one from oC10 core - Scenario Outline: Cannot create a share of a file with a user with only (create,delete) permission - Given using OCS API version "" - And user "Brian" has been created with default attributes and without skeleton files - When user "Alice" creates a share using the sharing API with settings - | path | textfile0.txt | - | shareWith | Brian | - | shareType | user | - | permissions | | - Then the OCS status code should be "" or "" - And the HTTP status code should be "" or "" - And as "Brian" entry "textfile0.txt" should not exist - Examples: - | ocs_api_version | eos_status_code | ocs_status_code | http_status_code_ocs | http_status_code_eos | permissions | - | 1 | 100 | 996 | 200 | 500 | delete | - | 2 | 200 | 996 | 200 | 500 | delete | - | 1 | 100 | 996 | 200 | 500 | create,delete | - | 2 | 200 | 996 | 200 | 500 | create,delete |