From 00567fa575056af80b1ddf0525ecd70976849f38 Mon Sep 17 00:00:00 2001 From: Roman Perekhod <2403905@gmail.com> Date: Wed, 21 Oct 2026 09:53:26 +0200 Subject: [PATCH] feat: [full-ci] [OCISDEV-1563] remove the owncloudsql driver (#13191) Remove the deprecated owncloudsql driver configuration from the storage-users, auth-basic, users, groups and sharing services and from the migrate command. Co-authored-by: Claude Sonnet 5.5 --- .../change-remove-owncloudsql-driver.md | 9 + go.mod | 2 +- go.sum | 4 +- ocis/pkg/command/migrate.go | 9 - services/auth-basic/pkg/config/config.go | 17 +- .../pkg/config/defaults/defaultconfig.go | 10 - services/auth-basic/pkg/revaconfig/config.go | 11 - services/groups/pkg/config/config.go | 18 +- .../pkg/config/defaults/defaultconfig.go | 12 - services/sharing/pkg/config/config.go | 18 +- .../pkg/config/defaults/defaultconfig.go | 7 - services/sharing/pkg/revaconfig/config.go | 9 - services/storage-users/pkg/config/config.go | 25 +- .../pkg/config/defaults/defaultconfig.go | 12 - .../storage-users/pkg/revaconfig/drivers.go | 23 - services/storage-users/pkg/revaconfig/user.go | 38 +- services/users/pkg/config/config.go | 17 +- .../pkg/config/defaults/defaultconfig.go | 12 - services/users/pkg/revaconfig/config.go | 12 - .../compress/flate/huffman_sortByFreq.go | 159 -- .../compress/flate/huffman_sortByLiteral.go | 201 -- .../reva/v2/pkg/auth/manager/loader/loader.go | 1 - .../manager/owncloudsql/accounts/accounts.go | 165 -- .../manager/owncloudsql/accounts/test.sqlite | Bin 90112 -> 0 bytes .../auth/manager/owncloudsql/owncloudsql.go | 192 -- .../pkg/publicshare/manager/loader/loader.go | 1 - .../manager/owncloudsql/conversions.go | 226 -- .../manager/owncloudsql/owncloudsql.go | 533 ---- .../publicshare/manager/owncloudsql/test.db | Bin 655360 -> 0 bytes .../reva/v2/pkg/rgrpc/todo/pool/selector.go | 120 +- .../v2/pkg/share/manager/loader/loader.go | 1 - .../share/manager/owncloudsql/conversions.go | 301 --- .../share/manager/owncloudsql/owncloudsql.go | 674 ------ .../v2/pkg/share/manager/owncloudsql/test.db | Bin 655360 -> 0 bytes .../reva/v2/pkg/storage/fs/loader/loader.go | 1 - .../fs/owncloudsql/filecache/filecache.go | 761 ------ .../storage/fs/owncloudsql/filecache/test.db | Bin 655360 -> 0 bytes .../pkg/storage/fs/owncloudsql/owncloudsql.go | 2149 ----------------- .../fs/owncloudsql/owncloudsql_unix.go | 87 - .../fs/owncloudsql/owncloudsql_windows.go | 76 - .../v2/pkg/storage/fs/owncloudsql/spaces.go | 193 -- .../v2/pkg/storage/fs/owncloudsql/upload.go | 43 - .../reva/v2/pkg/user/manager/loader/loader.go | 1 - .../manager/owncloudsql/accounts/accounts.go | 228 -- .../manager/owncloudsql/accounts/test.sqlite | Bin 90112 -> 0 bytes .../user/manager/owncloudsql/owncloudsql.go | 189 -- .../prometheus/procfs/internal/util/parse.go | 126 - .../procfs/internal/util/readfile.go | 37 - .../procfs/internal/util/sysreadfile.go | 68 - .../internal/util/sysreadfile_compat.go | 26 - .../procfs/internal/util/valueparser.go | 91 - vendor/modules.txt | 10 +- 52 files changed, 143 insertions(+), 6782 deletions(-) create mode 100644 changelog/unreleased/change-remove-owncloudsql-driver.md delete mode 100644 vendor/github.com/klauspost/compress/flate/huffman_sortByFreq.go delete mode 100644 vendor/github.com/klauspost/compress/flate/huffman_sortByLiteral.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/auth/manager/owncloudsql/accounts/accounts.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/auth/manager/owncloudsql/accounts/test.sqlite delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/auth/manager/owncloudsql/owncloudsql.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/conversions.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/owncloudsql.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/test.db delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/conversions.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/owncloudsql.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/test.db delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/filecache/filecache.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/filecache/test.db delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/owncloudsql.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/owncloudsql_unix.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/owncloudsql_windows.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/spaces.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/upload.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/user/manager/owncloudsql/accounts/accounts.go delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/user/manager/owncloudsql/accounts/test.sqlite delete mode 100644 vendor/github.com/owncloud/reva/v2/pkg/user/manager/owncloudsql/owncloudsql.go delete mode 100644 vendor/github.com/prometheus/procfs/internal/util/parse.go delete mode 100644 vendor/github.com/prometheus/procfs/internal/util/readfile.go delete mode 100644 vendor/github.com/prometheus/procfs/internal/util/sysreadfile.go delete mode 100644 vendor/github.com/prometheus/procfs/internal/util/sysreadfile_compat.go delete mode 100644 vendor/github.com/prometheus/procfs/internal/util/valueparser.go diff --git a/changelog/unreleased/change-remove-owncloudsql-driver.md b/changelog/unreleased/change-remove-owncloudsql-driver.md new file mode 100644 index 00000000000..8de8ccdbdea --- /dev/null +++ b/changelog/unreleased/change-remove-owncloudsql-driver.md @@ -0,0 +1,9 @@ +Change: Remove the owncloudsql driver + +We removed the deprecated `owncloudsql` driver from oCIS. + +The services `storage-users`, `auth-basic`, `users`, `groups`, and `sharing` no +longer accept the value `owncloudsql` for their driver. The related +configuration sections and environment variables are removed. + +https://github.com/owncloud/ocis/pull/13191 diff --git a/go.mod b/go.mod index 1eed206e320..39e0104f201 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( github.com/open-policy-agent/opa v1.21.1 github.com/orcaman/concurrent-map v1.0.0 github.com/owncloud/libre-graph-api-go v1.0.5-0.20260216101009-eeac018af245 - github.com/owncloud/reva/v2 v2.0.0-20261012083840-8b649a4c3899 + github.com/owncloud/reva/v2 v2.0.0-20261019155447-90c24c1b3575 github.com/pkg/errors v0.9.1 github.com/pkg/xattr v0.4.12 github.com/prometheus/client_golang v1.25.0 diff --git a/go.sum b/go.sum index df781950222..69f7d2c4ec1 100644 --- a/go.sum +++ b/go.sum @@ -736,8 +736,8 @@ github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HD github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= github.com/owncloud/libre-graph-api-go v1.0.5-0.20260216101009-eeac018af245 h1:JRidLTAKhnvyLMRtVtSF4lhBa0NSAOs6fof+d6JnKII= github.com/owncloud/libre-graph-api-go v1.0.5-0.20260216101009-eeac018af245/go.mod h1:z61VMGAJRtR1nbgXWiNoCkxUXP1B3Je9rMuJbnGd+Og= -github.com/owncloud/reva/v2 v2.0.0-20261012083840-8b649a4c3899 h1:IMu076AtnFEdabpBbowcGDYOeFLtm9ycNY1Z43YPYZU= -github.com/owncloud/reva/v2 v2.0.0-20261012083840-8b649a4c3899/go.mod h1:cgqg+UTe4ekjMbJj8hgoXSTxQ9fQramKcOUdvsFUrzc= +github.com/owncloud/reva/v2 v2.0.0-20261019155447-90c24c1b3575 h1:GOsblJHW/dvNdxxXmCb/PZOGRIg1ci4SVs+pA7hw6t4= +github.com/owncloud/reva/v2 v2.0.0-20261019155447-90c24c1b3575/go.mod h1:cgqg+UTe4ekjMbJj8hgoXSTxQ9fQramKcOUdvsFUrzc= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pablodz/inotifywaitgo v0.0.12 h1:DVJJTFMDlSAdO46yisPF2vzozs/r+4k6ih8MVghq78M= diff --git a/ocis/pkg/command/migrate.go b/ocis/pkg/command/migrate.go index a12a4d486e9..bbfea341089 100644 --- a/ocis/pkg/command/migrate.go +++ b/ocis/pkg/command/migrate.go @@ -501,15 +501,6 @@ func revaShareConfig(cfg *sharing.Config) map[string]interface{} { "enable_expired_shares_cleanup": cfg.UserSharingDrivers.SQL.EnableExpiredSharesCleanup, "janitor_run_interval": cfg.UserSharingDrivers.SQL.JanitorRunInterval, }, - "owncloudsql": map[string]interface{}{ - "gateway_addr": cfg.Reva.Address, - "storage_mount_id": cfg.UserSharingDrivers.OwnCloudSQL.UserStorageMountID, - "db_username": cfg.UserSharingDrivers.OwnCloudSQL.DBUsername, - "db_password": cfg.UserSharingDrivers.OwnCloudSQL.DBPassword, - "db_host": cfg.UserSharingDrivers.OwnCloudSQL.DBHost, - "db_port": cfg.UserSharingDrivers.OwnCloudSQL.DBPort, - "db_name": cfg.UserSharingDrivers.OwnCloudSQL.DBName, - }, "cs3": map[string]interface{}{ "gateway_addr": cfg.UserSharingDrivers.CS3.ProviderAddr, "provider_addr": cfg.UserSharingDrivers.CS3.ProviderAddr, diff --git a/services/auth-basic/pkg/config/config.go b/services/auth-basic/pkg/config/config.go index d73642b5be5..700f90563d5 100644 --- a/services/auth-basic/pkg/config/config.go +++ b/services/auth-basic/pkg/config/config.go @@ -52,9 +52,8 @@ type GRPCConfig struct { } type AuthProviders struct { - LDAP LDAPProvider `yaml:"ldap"` - OwnCloudSQL OwnCloudSQLProvider `yaml:"owncloudsql"` - JSON JSONProvider `yaml:"json,omitempty"` // not supported by the oCIS product, therefore not part of docs + LDAP LDAPProvider `yaml:"ldap"` + JSON JSONProvider `yaml:"json,omitempty"` // not supported by the oCIS product, therefore not part of docs } type JSONProvider struct { @@ -110,15 +109,3 @@ type LDAPGroupSchema struct { Groupname string `yaml:"group_name" env:"OCIS_LDAP_GROUP_SCHEMA_GROUPNAME;AUTH_BASIC_LDAP_GROUP_SCHEMA_GROUPNAME" desc:"LDAP Attribute to use for the name of groups." introductionVersion:"pre5.0"` Member string `yaml:"member" env:"OCIS_LDAP_GROUP_SCHEMA_MEMBER;AUTH_BASIC_LDAP_GROUP_SCHEMA_MEMBER" desc:"LDAP Attribute that is used for group members." introductionVersion:"pre5.0"` } - -type OwnCloudSQLProvider struct { - DBUsername string `yaml:"db_username" env:"AUTH_BASIC_OWNCLOUDSQL_DB_USERNAME" desc:"Database user to use for authenticating with the owncloud database." introductionVersion:"pre5.0"` - DBPassword string `yaml:"db_password" env:"AUTH_BASIC_OWNCLOUDSQL_DB_PASSWORD" desc:"Password for the database user." introductionVersion:"pre5.0"` - DBHost string `yaml:"db_host" env:"AUTH_BASIC_OWNCLOUDSQL_DB_HOST" desc:"Hostname of the database server." introductionVersion:"pre5.0"` - DBPort int `yaml:"db_port" env:"AUTH_BASIC_OWNCLOUDSQL_DB_PORT" desc:"Network port to use for the database connection." introductionVersion:"pre5.0"` - DBName string `yaml:"db_name" env:"AUTH_BASIC_OWNCLOUDSQL_DB_NAME" desc:"Name of the owncloud database." introductionVersion:"pre5.0"` - IDP string `yaml:"idp" env:"AUTH_BASIC_OWNCLOUDSQL_IDP" desc:"The identity provider value to set in the userids of the CS3 user objects for users returned by this user provider." introductionVersion:"pre5.0"` - Nobody int64 `yaml:"nobody" env:"AUTH_BASIC_OWNCLOUDSQL_NOBODY" desc:"Fallback number if no numeric UID and GID properties are provided." introductionVersion:"pre5.0"` - JoinUsername bool `yaml:"join_username" env:"AUTH_BASIC_OWNCLOUDSQL_JOIN_USERNAME" desc:"Join the user properties table to read usernames" introductionVersion:"pre5.0"` - JoinOwnCloudUUID bool `yaml:"join_owncloud_uuid" env:"AUTH_BASIC_OWNCLOUDSQL_JOIN_OWNCLOUD_UUID" desc:"Join the user properties table to read user ID's." introductionVersion:"pre5.0"` -} diff --git a/services/auth-basic/pkg/config/defaults/defaultconfig.go b/services/auth-basic/pkg/config/defaults/defaultconfig.go index d73394ccc25..6efd52c52d7 100644 --- a/services/auth-basic/pkg/config/defaults/defaultconfig.go +++ b/services/auth-basic/pkg/config/defaults/defaultconfig.go @@ -76,16 +76,6 @@ func DefaultConfig() *config.Config { }, }, JSON: config.JSONProvider{}, - OwnCloudSQL: config.OwnCloudSQLProvider{ - DBUsername: "owncloud", - DBHost: "mysql", - DBPort: 3306, - DBName: "owncloud", - IDP: "https://localhost:9200", - Nobody: 90, - JoinUsername: false, - JoinOwnCloudUUID: false, - }, }, } } diff --git a/services/auth-basic/pkg/revaconfig/config.go b/services/auth-basic/pkg/revaconfig/config.go index fd22a03d85a..e9f1a7c4ace 100644 --- a/services/auth-basic/pkg/revaconfig/config.go +++ b/services/auth-basic/pkg/revaconfig/config.go @@ -30,17 +30,6 @@ func AuthBasicConfigFromStruct(cfg *config.Config) map[string]interface{} { "users": cfg.AuthProviders.JSON.File, }, "ldap": ldapConfigFromString(cfg.AuthProviders.LDAP), - "owncloudsql": map[string]interface{}{ - "dbusername": cfg.AuthProviders.OwnCloudSQL.DBUsername, - "dbpassword": cfg.AuthProviders.OwnCloudSQL.DBPassword, - "dbhost": cfg.AuthProviders.OwnCloudSQL.DBHost, - "dbport": cfg.AuthProviders.OwnCloudSQL.DBPort, - "dbname": cfg.AuthProviders.OwnCloudSQL.DBName, - "idp": cfg.AuthProviders.OwnCloudSQL.IDP, - "nobody": cfg.AuthProviders.OwnCloudSQL.Nobody, - "join_username": cfg.AuthProviders.OwnCloudSQL.JoinUsername, - "join_ownclouduuid": cfg.AuthProviders.OwnCloudSQL.JoinOwnCloudUUID, - }, }, }, }, diff --git a/services/groups/pkg/config/config.go b/services/groups/pkg/config/config.go index a2225acfd1f..d07bf6d2eca 100644 --- a/services/groups/pkg/config/config.go +++ b/services/groups/pkg/config/config.go @@ -21,7 +21,7 @@ type Config struct { SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"GROUPS_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token." introductionVersion:"pre5.0"` - Driver string `yaml:"driver" env:"GROUPS_DRIVER" desc:"The driver which should be used by the groups service. Supported values are 'ldap' and 'owncloudsql'." introductionVersion:"pre5.0"` + Driver string `yaml:"driver" env:"GROUPS_DRIVER" desc:"The driver which should be used by the groups service. Supported value is 'ldap'." introductionVersion:"pre5.0"` Drivers Drivers `yaml:"drivers"` Context context.Context `yaml:"-"` @@ -53,8 +53,7 @@ type GRPCConfig struct { } type Drivers struct { - LDAP LDAPDriver `yaml:"ldap"` - OwnCloudSQL OwnCloudSQLDriver `yaml:"owncloudsql"` + LDAP LDAPDriver `yaml:"ldap"` JSON JSONDriver `yaml:"json,omitempty"` // not supported by the oCIS product, therefore not part of docs REST RESTProvider `yaml:"rest,omitempty"` // not supported by the oCIS product, therefore not part of docs @@ -107,19 +106,6 @@ type LDAPGroupSchema struct { Member string `yaml:"member" env:"OCIS_LDAP_GROUP_SCHEMA_MEMBER;GROUPS_LDAP_GROUP_SCHEMA_MEMBER" desc:"LDAP Attribute that is used for group members." introductionVersion:"pre5.0"` } -type OwnCloudSQLDriver struct { - DBUsername string `yaml:"db_username" env:"GROUPS_OWNCLOUDSQL_DB_USERNAME" desc:"Database user to use for authenticating with the owncloud database." introductionVersion:"pre5.0"` - DBPassword string `yaml:"db_password" env:"GROUPS_OWNCLOUDSQL_DB_PASSWORD" desc:"Password for the database user." introductionVersion:"pre5.0"` - DBHost string `yaml:"db_host" env:"GROUPS_OWNCLOUDSQL_DB_HOST" desc:"Hostname of the database server." introductionVersion:"pre5.0"` - DBPort int `yaml:"db_port" env:"GROUPS_OWNCLOUDSQL_DB_PORT" desc:"Network port to use for the database connection." introductionVersion:"pre5.0"` - DBName string `yaml:"db_name" env:"GROUPS_OWNCLOUDSQL_DB_NAME" desc:"Name of the owncloud database." introductionVersion:"pre5.0"` - IDP string `yaml:"idp" env:"GROUPS_OWNCLOUDSQL_IDP" desc:"The identity provider value to set in the userids of the CS3 user objects for users returned by this user provider." introductionVersion:"pre5.0"` - Nobody int64 `yaml:"nobody" env:"GROUPS_OWNCLOUDSQL_NOBODY" desc:"Fallback number if no numeric UID and GID properties are provided." introductionVersion:"pre5.0"` - JoinUsername bool `yaml:"join_username" env:"GROUPS_OWNCLOUDSQL_JOIN_USERNAME" desc:"Join the user properties table to read usernames." introductionVersion:"pre5.0"` - JoinOwnCloudUUID bool `yaml:"join_owncloud_uuid" env:"GROUPS_OWNCLOUDSQL_JOIN_OWNCLOUD_UUID" desc:"Join the user properties table to read user IDs." introductionVersion:"pre5.0"` - EnableMedialSearch bool `yaml:"enable_medial_search" env:"GROUPS_OWNCLOUDSQL_ENABLE_MEDIAL_SEARCH" desc:"Allow 'medial search' when searching for users instead of just doing a prefix search. This allows finding 'Alice' when searching for 'lic'." introductionVersion:"pre5.0"` -} - type JSONDriver struct { File string } diff --git a/services/groups/pkg/config/defaults/defaultconfig.go b/services/groups/pkg/config/defaults/defaultconfig.go index f40ef3b3345..0f77525900f 100644 --- a/services/groups/pkg/config/defaults/defaultconfig.go +++ b/services/groups/pkg/config/defaults/defaultconfig.go @@ -72,18 +72,6 @@ func DefaultConfig() *config.Config { Member: "member", }, }, - OwnCloudSQL: config.OwnCloudSQLDriver{ - DBUsername: "owncloud", - DBPassword: "", - DBHost: "mysql", - DBPort: 3306, - DBName: "owncloud", - IDP: "https://localhost:9200", - Nobody: 90, - JoinUsername: false, - JoinOwnCloudUUID: false, - EnableMedialSearch: false, - }, }, } } diff --git a/services/sharing/pkg/config/config.go b/services/sharing/pkg/config/config.go index b1d6aecd654..f44ffdf59ad 100644 --- a/services/sharing/pkg/config/config.go +++ b/services/sharing/pkg/config/config.go @@ -21,7 +21,7 @@ type Config struct { SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"SHARING_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token." introductionVersion:"pre5.0"` - UserSharingDriver string `yaml:"user_sharing_driver" env:"SHARING_USER_DRIVER" desc:"Driver to be used to persist shares. Supported values are 'jsoncs3', 'json', 'cs3' (deprecated) and 'owncloudsql'." introductionVersion:"pre5.0"` + UserSharingDriver string `yaml:"user_sharing_driver" env:"SHARING_USER_DRIVER" desc:"Driver to be used to persist shares. Supported values are 'jsoncs3', 'json' and 'cs3' (deprecated)." introductionVersion:"pre5.0"` UserSharingDrivers UserSharingDrivers `yaml:"user_sharing_drivers"` PublicSharingDriver string `yaml:"public_sharing_driver" env:"SHARING_PUBLIC_DRIVER" desc:"Driver to be used to persist public shares. Supported values are 'jsoncs3', 'json' and 'cs3' (deprecated)." introductionVersion:"pre5.0"` PublicSharingDrivers PublicSharingDrivers `yaml:"public_sharing_drivers"` @@ -62,10 +62,9 @@ type GRPCConfig struct { } type UserSharingDrivers struct { - JSONCS3 UserSharingJSONCS3Driver `yaml:"jsoncs3"` - JSON UserSharingJSONDriver `yaml:"json"` - CS3 UserSharingCS3Driver `yaml:"cs3"` - OwnCloudSQL UserSharingOwnCloudSQLDriver `yaml:"owncloudsql"` + JSONCS3 UserSharingJSONCS3Driver `yaml:"jsoncs3"` + JSON UserSharingJSONDriver `yaml:"json"` + CS3 UserSharingCS3Driver `yaml:"cs3"` SQL UserSharingSQLDriver `yaml:"sql,omitempty"` // not supported by the oCIS product, therefore not part of docs } @@ -86,15 +85,6 @@ type UserSharingSQLDriver struct { UserStorageMountID string `yaml:"user_storage_mount_id"` } -type UserSharingOwnCloudSQLDriver struct { - DBUsername string `yaml:"db_username" env:"SHARING_USER_OWNCLOUDSQL_DB_USERNAME" desc:"Username for the database." introductionVersion:"pre5.0"` - DBPassword string `yaml:"db_password" env:"SHARING_USER_OWNCLOUDSQL_DB_PASSWORD" desc:"Password for the database." introductionVersion:"pre5.0"` - DBHost string `yaml:"db_host" env:"SHARING_USER_OWNCLOUDSQL_DB_HOST" desc:"Hostname or IP of the database server." introductionVersion:"pre5.0"` - DBPort int `yaml:"db_port" env:"SHARING_USER_OWNCLOUDSQL_DB_PORT" desc:"Port that the database server is listening on." introductionVersion:"pre5.0"` - DBName string `yaml:"db_name" env:"SHARING_USER_OWNCLOUDSQL_DB_NAME" desc:"Name of the database to be used." introductionVersion:"pre5.0"` - UserStorageMountID string `yaml:"user_storage_mount_id" env:"SHARING_USER_OWNCLOUDSQL_USER_STORAGE_MOUNT_ID" desc:"Mount ID of the ownCloudSQL users storage for mapping ownCloud 10 shares." introductionVersion:"pre5.0"` -} - type UserSharingCS3Driver struct { ProviderAddr string `yaml:"provider_addr" env:"SHARING_USER_CS3_PROVIDER_ADDR" desc:"GRPC address of the STORAGE-SYSTEM service." introductionVersion:"pre5.0"` SystemUserID string `yaml:"system_user_id" env:"OCIS_SYSTEM_USER_ID;SHARING_USER_CS3_SYSTEM_USER_ID" desc:"ID of the oCIS STORAGE-SYSTEM system user. Admins need to set the ID for the STORAGE-SYSTEM system user in this config option which is then used to reference the user. Any reasonable long string is possible, preferably this would be an UUIDv4 format." introductionVersion:"pre5.0"` diff --git a/services/sharing/pkg/config/defaults/defaultconfig.go b/services/sharing/pkg/config/defaults/defaultconfig.go index 10a76ef89eb..fec9b3a6526 100644 --- a/services/sharing/pkg/config/defaults/defaultconfig.go +++ b/services/sharing/pkg/config/defaults/defaultconfig.go @@ -49,12 +49,6 @@ func DefaultConfig() *config.Config { SystemUserIDP: "internal", MaxConcurrency: 1, }, - OwnCloudSQL: config.UserSharingOwnCloudSQLDriver{ - DBUsername: "owncloud", - DBHost: "mysql", - DBPort: 3306, - DBName: "owncloud", - }, }, PublicSharingDriver: "jsoncs3", PublicSharingDrivers: config.PublicSharingDrivers{ @@ -69,7 +63,6 @@ func DefaultConfig() *config.Config { ProviderAddr: "com.owncloud.api.storage-system", // system storage SystemUserIDP: "internal", }, - // TODO implement and add owncloudsql publicshare driver }, Events: config.Events{ Addr: "127.0.0.1:9233", diff --git a/services/sharing/pkg/revaconfig/config.go b/services/sharing/pkg/revaconfig/config.go index d5021ee21d6..d68a7e44c7d 100644 --- a/services/sharing/pkg/revaconfig/config.go +++ b/services/sharing/pkg/revaconfig/config.go @@ -54,15 +54,6 @@ func SharingConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string] "enable_expired_shares_cleanup": cfg.UserSharingDrivers.SQL.EnableExpiredSharesCleanup, "janitor_run_interval": cfg.UserSharingDrivers.SQL.JanitorRunInterval, }, - "owncloudsql": map[string]interface{}{ - "gateway_addr": cfg.Reva.Address, - "storage_mount_id": cfg.UserSharingDrivers.OwnCloudSQL.UserStorageMountID, - "db_username": cfg.UserSharingDrivers.OwnCloudSQL.DBUsername, - "db_password": cfg.UserSharingDrivers.OwnCloudSQL.DBPassword, - "db_host": cfg.UserSharingDrivers.OwnCloudSQL.DBHost, - "db_port": cfg.UserSharingDrivers.OwnCloudSQL.DBPort, - "db_name": cfg.UserSharingDrivers.OwnCloudSQL.DBName, - }, "cs3": map[string]interface{}{ "gateway_addr": cfg.UserSharingDrivers.CS3.ProviderAddr, "provider_addr": cfg.UserSharingDrivers.CS3.ProviderAddr, diff --git a/services/storage-users/pkg/config/config.go b/services/storage-users/pkg/config/config.go index f16e9ad322d..72391411a7a 100644 --- a/services/storage-users/pkg/config/config.go +++ b/services/storage-users/pkg/config/config.go @@ -24,7 +24,7 @@ type Config struct { SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"STORAGE_USERS_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token." introductionVersion:"pre5.0"` GracefulShutdownTimeout int `yaml:"graceful_shutdown_timeout" env:"STORAGE_USERS_GRACEFUL_SHUTDOWN_TIMEOUT" desc:"The number of seconds to wait for the 'storage-users' service to shutdown cleanly before exiting with an error that gets logged. Note: This setting is only applicable when running the 'storage-users' service as a standalone service. See the text description for more details." introductionVersion:"pre5.0"` - Driver string `yaml:"driver" env:"STORAGE_USERS_DRIVER" desc:"The storage driver which should be used by the service. Defaults to 'ocis', Supported values are: 'ocis', 's3ng' and 'owncloudsql' (deprecated). The 'ocis' driver stores all data (blob and meta data) in an POSIX compliant volume. The 's3ng' driver stores metadata in a POSIX compliant volume and uploads blobs to the s3 bucket." introductionVersion:"pre5.0"` + Driver string `yaml:"driver" env:"STORAGE_USERS_DRIVER" desc:"The storage driver which should be used by the service. Defaults to 'ocis', Supported values are: 'ocis' and 's3ng'. The 'ocis' driver stores all data (blob and meta data) in an POSIX compliant volume. The 's3ng' driver stores metadata in a POSIX compliant volume and uploads blobs to the s3 bucket." introductionVersion:"pre5.0"` Drivers Drivers `yaml:"drivers"` DataServerURL string `yaml:"data_server_url" env:"STORAGE_USERS_DATA_SERVER_URL" desc:"URL of the data server, needs to be reachable by the data gateway provided by the frontend service or the user if directly exposed." introductionVersion:"pre5.0"` DataGatewayURL string `yaml:"data_gateway_url" env:"STORAGE_USERS_DATA_GATEWAY_URL" desc:"URL of the data gateway server" introductionVersion:"pre5.0"` @@ -100,10 +100,9 @@ type CORS struct { // Drivers combine all storage driver configurations type Drivers struct { - OCIS OCISDriver `yaml:"ocis"` - S3NG S3NGDriver `yaml:"s3ng"` - OwnCloudSQL OwnCloudSQLDriver `yaml:"owncloudsql"` - Posix PosixDriver `yaml:"posix"` + OCIS OCISDriver `yaml:"ocis"` + S3NG S3NGDriver `yaml:"s3ng"` + Posix PosixDriver `yaml:"posix"` S3 S3Driver `yaml:",omitempty"` // not supported by the oCIS product, therefore not part of docs EOS EOSDriver `yaml:",omitempty"` // not supported by the oCIS product, therefore not part of docs @@ -176,22 +175,6 @@ type S3NGDriver struct { DisableVersioning bool `yaml:"disable_versioning" env:"OCIS_DISABLE_VERSIONING" desc:"Disables versioning of files. When set to true, new uploads with the same filename will overwrite existing files instead of creating a new version." introductionVersion:"7.0.0"` } -// OwnCloudSQLDriver is the storage driver configuration when using 'owncloudsql' storage driver -type OwnCloudSQLDriver struct { - // Root is the absolute path to the location of the data - Root string `yaml:"root" env:"STORAGE_USERS_OWNCLOUDSQL_DATADIR" desc:"The directory where the filesystem storage will store SQL migration data. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH/storage/owncloud." introductionVersion:"pre5.0"` - // ShareFolder defines the name of the folder jailing all shares - ShareFolder string `yaml:"share_folder" env:"STORAGE_USERS_OWNCLOUDSQL_SHARE_FOLDER" desc:"Name of the folder jailing all shares." introductionVersion:"pre5.0"` - UserLayout string `yaml:"user_layout" env:"STORAGE_USERS_OWNCLOUDSQL_LAYOUT" desc:"Path layout to use to navigate into a users folder in an owncloud data directory" introductionVersion:"pre5.0"` - UploadInfoDir string `yaml:"upload_info_dir" env:"STORAGE_USERS_OWNCLOUDSQL_UPLOADINFO_DIR" desc:"The directory where the filesystem will store uploads temporarily. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH/storage/uploadinfo." introductionVersion:"pre5.0"` - DBUsername string `yaml:"db_username" env:"STORAGE_USERS_OWNCLOUDSQL_DB_USERNAME" desc:"Username for the database." introductionVersion:"pre5.0"` - DBPassword string `yaml:"db_password" env:"STORAGE_USERS_OWNCLOUDSQL_DB_PASSWORD" desc:"Password for the database." introductionVersion:"pre5.0"` - DBHost string `yaml:"db_host" env:"STORAGE_USERS_OWNCLOUDSQL_DB_HOST" desc:"Hostname or IP of the database server." introductionVersion:"pre5.0"` - DBPort int `yaml:"db_port" env:"STORAGE_USERS_OWNCLOUDSQL_DB_PORT" desc:"Port that the database server is listening on." introductionVersion:"pre5.0"` - DBName string `yaml:"db_name" env:"STORAGE_USERS_OWNCLOUDSQL_DB_NAME" desc:"Name of the database to be used." introductionVersion:"pre5.0"` - UsersProviderEndpoint string `yaml:"users_provider_endpoint" env:"STORAGE_USERS_OWNCLOUDSQL_USERS_PROVIDER_ENDPOINT" desc:"Endpoint of the users provider." introductionVersion:"pre5.0"` -} - // PosixDriver is the storage driver configuration when using 'posix' storage driver type PosixDriver struct { // Root is the absolute path to the location of the data diff --git a/services/storage-users/pkg/config/defaults/defaultconfig.go b/services/storage-users/pkg/config/defaults/defaultconfig.go index f77ef6372f5..78d9136dd16 100644 --- a/services/storage-users/pkg/config/defaults/defaultconfig.go +++ b/services/storage-users/pkg/config/defaults/defaultconfig.go @@ -94,18 +94,6 @@ func DefaultConfig() *config.Config { GracefulShutdownTimeout: 30, Driver: "ocis", Drivers: config.Drivers{ - OwnCloudSQL: config.OwnCloudSQLDriver{ - Root: filepath.Join(defaults.BaseDataPath(), "storage", "owncloud"), - ShareFolder: "/Shares", - UserLayout: "{{.Username}}", - UploadInfoDir: filepath.Join(defaults.BaseDataPath(), "storage", "uploadinfo"), - DBUsername: "owncloud", - DBPassword: "owncloud", - DBHost: "", - DBPort: 3306, - DBName: "owncloud", - UsersProviderEndpoint: "com.owncloud.api.users", - }, S3NG: config.S3NGDriver{ Propagator: "sync", Root: filepath.Join(defaults.BaseDataPath(), "storage", "users"), diff --git a/services/storage-users/pkg/revaconfig/drivers.go b/services/storage-users/pkg/revaconfig/drivers.go index 0c59be695b6..c4a6cb1461f 100644 --- a/services/storage-users/pkg/revaconfig/drivers.go +++ b/services/storage-users/pkg/revaconfig/drivers.go @@ -137,29 +137,6 @@ func LocalHome(cfg *config.Config) map[string]interface{} { } } -// OwnCloudSQL is the config mapping for the OwnCloudSQL storage driver -func OwnCloudSQL(cfg *config.Config) map[string]interface{} { - return map[string]interface{}{ - "datadirectory": cfg.Drivers.OwnCloudSQL.Root, - "upload_info_dir": cfg.Drivers.OwnCloudSQL.UploadInfoDir, - "share_folder": cfg.Drivers.OwnCloudSQL.ShareFolder, - "user_layout": cfg.Drivers.OwnCloudSQL.UserLayout, - "enable_home": false, - "dbusername": cfg.Drivers.OwnCloudSQL.DBUsername, - "dbpassword": cfg.Drivers.OwnCloudSQL.DBPassword, - "dbhost": cfg.Drivers.OwnCloudSQL.DBHost, - "dbport": cfg.Drivers.OwnCloudSQL.DBPort, - "dbname": cfg.Drivers.OwnCloudSQL.DBName, - "userprovidersvc": cfg.Drivers.OwnCloudSQL.UsersProviderEndpoint, - "tokens": map[string]interface{}{ - "download_endpoint": cfg.DataServerURL, - "datagateway_endpoint": cfg.DataGatewayURL, - "transfer_shared_secret": cfg.Commons.TransferSecret, - "transfer_expires": cfg.TransferExpires, - }, - } -} - // Ocis is the config mapping for the Ocis storage driver func Ocis(cfg *config.Config) map[string]interface{} { return map[string]interface{}{ diff --git a/services/storage-users/pkg/revaconfig/user.go b/services/storage-users/pkg/revaconfig/user.go index a40ba3d64e6..faa31cc5ecd 100644 --- a/services/storage-users/pkg/revaconfig/user.go +++ b/services/storage-users/pkg/revaconfig/user.go @@ -5,31 +5,29 @@ import "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" // StorageProviderDrivers are the drivers for the storage provider func StorageProviderDrivers(cfg *config.Config) map[string]interface{} { return map[string]interface{}{ - "eos": EOS(cfg), - "eoshome": EOSHome(cfg), - "eosgrpc": EOSGRPC(cfg), - "local": Local(cfg), - "localhome": LocalHome(cfg), - "owncloudsql": OwnCloudSQL(cfg), - "ocis": OcisNoEvents(cfg), - "s3": S3(cfg), - "s3ng": S3NGNoEvents(cfg), - "posix": Posix(cfg, true), + "eos": EOS(cfg), + "eoshome": EOSHome(cfg), + "eosgrpc": EOSGRPC(cfg), + "local": Local(cfg), + "localhome": LocalHome(cfg), + "ocis": OcisNoEvents(cfg), + "s3": S3(cfg), + "s3ng": S3NGNoEvents(cfg), + "posix": Posix(cfg, true), } } // DataProviderDrivers are the drivers for the storage provider func DataProviderDrivers(cfg *config.Config) map[string]interface{} { return map[string]interface{}{ - "eos": EOS(cfg), - "eoshome": EOSHome(cfg), - "eosgrpc": EOSGRPC(cfg), - "local": Local(cfg), - "localhome": LocalHome(cfg), - "owncloudsql": OwnCloudSQL(cfg), - "ocis": Ocis(cfg), - "s3": S3(cfg), - "s3ng": S3NG(cfg), - "posix": Posix(cfg, false), + "eos": EOS(cfg), + "eoshome": EOSHome(cfg), + "eosgrpc": EOSGRPC(cfg), + "local": Local(cfg), + "localhome": LocalHome(cfg), + "ocis": Ocis(cfg), + "s3": S3(cfg), + "s3ng": S3NG(cfg), + "posix": Posix(cfg, false), } } diff --git a/services/users/pkg/config/config.go b/services/users/pkg/config/config.go index 923d0f45caa..4876ec23656 100644 --- a/services/users/pkg/config/config.go +++ b/services/users/pkg/config/config.go @@ -21,7 +21,7 @@ type Config struct { SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"USERS_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token." introductionVersion:"pre5.0"` - Driver string `yaml:"driver" env:"USERS_DRIVER" desc:"The driver which should be used by the users service. Supported values are 'ldap' and 'owncloudsql'." introductionVersion:"pre5.0"` + Driver string `yaml:"driver" env:"USERS_DRIVER" desc:"The driver which should be used by the users service. Supported value is 'ldap'." introductionVersion:"pre5.0"` Drivers Drivers `yaml:"drivers"` Context context.Context `yaml:"-"` @@ -52,8 +52,7 @@ type GRPCConfig struct { } type Drivers struct { - LDAP LDAPDriver `yaml:"ldap"` - OwnCloudSQL OwnCloudSQLDriver `yaml:"owncloudsql"` + LDAP LDAPDriver `yaml:"ldap"` JSON JSONDriver `yaml:"json,omitempty"` // not supported by the oCIS product, therefore not part of docs REST RESTProvider `yaml:"rest,omitempty"` // not supported by the oCIS product, therefore not part of docs @@ -113,18 +112,6 @@ type LDAPGroupSchema struct { Member string `yaml:"member" env:"OCIS_LDAP_GROUP_SCHEMA_MEMBER;USERS_LDAP_GROUP_SCHEMA_MEMBER" desc:"LDAP Attribute that is used for group members." introductionVersion:"pre5.0"` } -type OwnCloudSQLDriver struct { - DBUsername string `yaml:"db_username" env:"USERS_OWNCLOUDSQL_DB_USERNAME" desc:"Database user to use for authenticating with the owncloud database." introductionVersion:"pre5.0"` - DBPassword string `yaml:"db_password" env:"USERS_OWNCLOUDSQL_DB_PASSWORD" desc:"Password for the database user." introductionVersion:"pre5.0"` - DBHost string `yaml:"db_host" env:"USERS_OWNCLOUDSQL_DB_HOST" desc:"Hostname of the database server." introductionVersion:"pre5.0"` - DBPort int `yaml:"db_port" env:"USERS_OWNCLOUDSQL_DB_PORT" desc:"Network port to use for the database connection." introductionVersion:"pre5.0"` - DBName string `yaml:"db_name" env:"USERS_OWNCLOUDSQL_DB_NAME" desc:"Name of the owncloud database." introductionVersion:"pre5.0"` - IDP string `yaml:"idp" env:"USERS_OWNCLOUDSQL_IDP" desc:"The identity provider value to set in the userids of the CS3 user objects for users returned by this user provider." introductionVersion:"pre5.0"` - Nobody int64 `yaml:"nobody" env:"USERS_OWNCLOUDSQL_NOBODY" desc:"Fallback number if no numeric UID and GID properties are provided." introductionVersion:"pre5.0"` - JoinUsername bool `yaml:"join_username" env:"USERS_OWNCLOUDSQL_JOIN_USERNAME" desc:"Join the user properties table to read usernames" introductionVersion:"pre5.0"` - JoinOwnCloudUUID bool `yaml:"join_owncloud_uuid" env:"USERS_OWNCLOUDSQL_JOIN_OWNCLOUD_UUID" desc:"Join the user properties table to read user IDs." introductionVersion:"pre5.0"` - EnableMedialSearch bool `yaml:"enable_medial_search" env:"USERS_OWNCLOUDSQL_ENABLE_MEDIAL_SEARCH" desc:"Allow 'medial search' when searching for users instead of just doing a prefix search. This allows finding 'Alice' when searching for 'lic'." introductionVersion:"pre5.0"` -} type RESTProvider struct { ClientID string ClientSecret string diff --git a/services/users/pkg/config/defaults/defaultconfig.go b/services/users/pkg/config/defaults/defaultconfig.go index 28933e2ad3a..6313f88efd3 100644 --- a/services/users/pkg/config/defaults/defaultconfig.go +++ b/services/users/pkg/config/defaults/defaultconfig.go @@ -77,18 +77,6 @@ func DefaultConfig() *config.Config { }, }, JSON: config.JSONDriver{}, - OwnCloudSQL: config.OwnCloudSQLDriver{ - DBUsername: "owncloud", - DBPassword: "secret", - DBHost: "mysql", - DBPort: 3306, - DBName: "owncloud", - IDP: "https://localhost:9200", - Nobody: 90, - JoinUsername: false, - JoinOwnCloudUUID: false, - EnableMedialSearch: false, - }, }, } } diff --git a/services/users/pkg/revaconfig/config.go b/services/users/pkg/revaconfig/config.go index 93398571779..7dc9d142d3f 100644 --- a/services/users/pkg/revaconfig/config.go +++ b/services/users/pkg/revaconfig/config.go @@ -30,18 +30,6 @@ func UsersConfigFromStruct(cfg *config.Config) map[string]interface{} { "users": cfg.Drivers.JSON.File, }, "ldap": ldapConfigFromString(cfg.Drivers.LDAP), - "owncloudsql": map[string]interface{}{ - "dbusername": cfg.Drivers.OwnCloudSQL.DBUsername, - "dbpassword": cfg.Drivers.OwnCloudSQL.DBPassword, - "dbhost": cfg.Drivers.OwnCloudSQL.DBHost, - "dbport": cfg.Drivers.OwnCloudSQL.DBPort, - "dbname": cfg.Drivers.OwnCloudSQL.DBName, - "idp": cfg.Drivers.OwnCloudSQL.IDP, - "nobody": cfg.Drivers.OwnCloudSQL.Nobody, - "join_username": cfg.Drivers.OwnCloudSQL.JoinUsername, - "join_ownclouduuid": cfg.Drivers.OwnCloudSQL.JoinOwnCloudUUID, - "enable_medial_search": cfg.Drivers.OwnCloudSQL.EnableMedialSearch, - }, }, }, }, diff --git a/vendor/github.com/klauspost/compress/flate/huffman_sortByFreq.go b/vendor/github.com/klauspost/compress/flate/huffman_sortByFreq.go deleted file mode 100644 index 6c05ba8c1c2..00000000000 --- a/vendor/github.com/klauspost/compress/flate/huffman_sortByFreq.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package flate - -// Sort sorts data. -// It makes one call to data.Len to determine n, and O(n*log(n)) calls to -// data.Less and data.Swap. The sort is not guaranteed to be stable. -func sortByFreq(data []literalNode) { - n := len(data) - quickSortByFreq(data, 0, n, maxDepth(n)) -} - -func quickSortByFreq(data []literalNode, a, b, maxDepth int) { - for b-a > 12 { // Use ShellSort for slices <= 12 elements - if maxDepth == 0 { - heapSort(data, a, b) - return - } - maxDepth-- - mlo, mhi := doPivotByFreq(data, a, b) - // Avoiding recursion on the larger subproblem guarantees - // a stack depth of at most lg(b-a). - if mlo-a < b-mhi { - quickSortByFreq(data, a, mlo, maxDepth) - a = mhi // i.e., quickSortByFreq(data, mhi, b) - } else { - quickSortByFreq(data, mhi, b, maxDepth) - b = mlo // i.e., quickSortByFreq(data, a, mlo) - } - } - if b-a > 1 { - // Do ShellSort pass with gap 6 - // It could be written in this simplified form cause b-a <= 12 - for i := a + 6; i < b; i++ { - if data[i].freq == data[i-6].freq && data[i].literal < data[i-6].literal || data[i].freq < data[i-6].freq { - data[i], data[i-6] = data[i-6], data[i] - } - } - insertionSortByFreq(data, a, b) - } -} - -func doPivotByFreq(data []literalNode, lo, hi int) (midlo, midhi int) { - m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow. - if hi-lo > 40 { - // Tukey's ``Ninther,'' median of three medians of three. - s := (hi - lo) / 8 - medianOfThreeSortByFreq(data, lo, lo+s, lo+2*s) - medianOfThreeSortByFreq(data, m, m-s, m+s) - medianOfThreeSortByFreq(data, hi-1, hi-1-s, hi-1-2*s) - } - medianOfThreeSortByFreq(data, lo, m, hi-1) - - // Invariants are: - // data[lo] = pivot (set up by ChoosePivot) - // data[lo < i < a] < pivot - // data[a <= i < b] <= pivot - // data[b <= i < c] unexamined - // data[c <= i < hi-1] > pivot - // data[hi-1] >= pivot - pivot := lo - a, c := lo+1, hi-1 - - for ; a < c && (data[a].freq == data[pivot].freq && data[a].literal < data[pivot].literal || data[a].freq < data[pivot].freq); a++ { - } - b := a - for { - for ; b < c && (data[pivot].freq == data[b].freq && data[pivot].literal > data[b].literal || data[pivot].freq > data[b].freq); b++ { // data[b] <= pivot - } - for ; b < c && (data[pivot].freq == data[c-1].freq && data[pivot].literal < data[c-1].literal || data[pivot].freq < data[c-1].freq); c-- { // data[c-1] > pivot - } - if b >= c { - break - } - // data[b] > pivot; data[c-1] <= pivot - data[b], data[c-1] = data[c-1], data[b] - b++ - c-- - } - // If hi-c<3 then there are duplicates (by property of median of nine). - // Let's be a bit more conservative, and set border to 5. - protect := hi-c < 5 - if !protect && hi-c < (hi-lo)/4 { - // Lets test some points for equality to pivot - dups := 0 - if data[pivot].freq == data[hi-1].freq && data[pivot].literal > data[hi-1].literal || data[pivot].freq > data[hi-1].freq { // data[hi-1] = pivot - data[c], data[hi-1] = data[hi-1], data[c] - c++ - dups++ - } - if data[b-1].freq == data[pivot].freq && data[b-1].literal > data[pivot].literal || data[b-1].freq > data[pivot].freq { // data[b-1] = pivot - b-- - dups++ - } - // m-lo = (hi-lo)/2 > 6 - // b-lo > (hi-lo)*3/4-1 > 8 - // ==> m < b ==> data[m] <= pivot - if data[m].freq == data[pivot].freq && data[m].literal > data[pivot].literal || data[m].freq > data[pivot].freq { // data[m] = pivot - data[m], data[b-1] = data[b-1], data[m] - b-- - dups++ - } - // if at least 2 points are equal to pivot, assume skewed distribution - protect = dups > 1 - } - if protect { - // Protect against a lot of duplicates - // Add invariant: - // data[a <= i < b] unexamined - // data[b <= i < c] = pivot - for { - for ; a < b && (data[b-1].freq == data[pivot].freq && data[b-1].literal > data[pivot].literal || data[b-1].freq > data[pivot].freq); b-- { // data[b] == pivot - } - for ; a < b && (data[a].freq == data[pivot].freq && data[a].literal < data[pivot].literal || data[a].freq < data[pivot].freq); a++ { // data[a] < pivot - } - if a >= b { - break - } - // data[a] == pivot; data[b-1] < pivot - data[a], data[b-1] = data[b-1], data[a] - a++ - b-- - } - } - // Swap pivot into middle - data[pivot], data[b-1] = data[b-1], data[pivot] - return b - 1, c -} - -// Insertion sort -func insertionSortByFreq(data []literalNode, a, b int) { - for i := a + 1; i < b; i++ { - for j := i; j > a && (data[j].freq == data[j-1].freq && data[j].literal < data[j-1].literal || data[j].freq < data[j-1].freq); j-- { - data[j], data[j-1] = data[j-1], data[j] - } - } -} - -// quickSortByFreq, loosely following Bentley and McIlroy, -// ``Engineering a Sort Function,'' SP&E November 1993. - -// medianOfThreeSortByFreq moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. -func medianOfThreeSortByFreq(data []literalNode, m1, m0, m2 int) { - // sort 3 elements - if data[m1].freq == data[m0].freq && data[m1].literal < data[m0].literal || data[m1].freq < data[m0].freq { - data[m1], data[m0] = data[m0], data[m1] - } - // data[m0] <= data[m1] - if data[m2].freq == data[m1].freq && data[m2].literal < data[m1].literal || data[m2].freq < data[m1].freq { - data[m2], data[m1] = data[m1], data[m2] - // data[m0] <= data[m2] && data[m1] < data[m2] - if data[m1].freq == data[m0].freq && data[m1].literal < data[m0].literal || data[m1].freq < data[m0].freq { - data[m1], data[m0] = data[m0], data[m1] - } - } - // now data[m0] <= data[m1] <= data[m2] -} diff --git a/vendor/github.com/klauspost/compress/flate/huffman_sortByLiteral.go b/vendor/github.com/klauspost/compress/flate/huffman_sortByLiteral.go deleted file mode 100644 index 93f1aea109e..00000000000 --- a/vendor/github.com/klauspost/compress/flate/huffman_sortByLiteral.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package flate - -// Sort sorts data. -// It makes one call to data.Len to determine n, and O(n*log(n)) calls to -// data.Less and data.Swap. The sort is not guaranteed to be stable. -func sortByLiteral(data []literalNode) { - n := len(data) - quickSort(data, 0, n, maxDepth(n)) -} - -func quickSort(data []literalNode, a, b, maxDepth int) { - for b-a > 12 { // Use ShellSort for slices <= 12 elements - if maxDepth == 0 { - heapSort(data, a, b) - return - } - maxDepth-- - mlo, mhi := doPivot(data, a, b) - // Avoiding recursion on the larger subproblem guarantees - // a stack depth of at most lg(b-a). - if mlo-a < b-mhi { - quickSort(data, a, mlo, maxDepth) - a = mhi // i.e., quickSort(data, mhi, b) - } else { - quickSort(data, mhi, b, maxDepth) - b = mlo // i.e., quickSort(data, a, mlo) - } - } - if b-a > 1 { - // Do ShellSort pass with gap 6 - // It could be written in this simplified form cause b-a <= 12 - for i := a + 6; i < b; i++ { - if data[i].literal < data[i-6].literal { - data[i], data[i-6] = data[i-6], data[i] - } - } - insertionSort(data, a, b) - } -} -func heapSort(data []literalNode, a, b int) { - first := a - lo := 0 - hi := b - a - - // Build heap with greatest element at top. - for i := (hi - 1) / 2; i >= 0; i-- { - siftDown(data, i, hi, first) - } - - // Pop elements, largest first, into end of data. - for i := hi - 1; i >= 0; i-- { - data[first], data[first+i] = data[first+i], data[first] - siftDown(data, lo, i, first) - } -} - -// siftDown implements the heap property on data[lo, hi). -// first is an offset into the array where the root of the heap lies. -func siftDown(data []literalNode, lo, hi, first int) { - root := lo - for { - child := 2*root + 1 - if child >= hi { - break - } - if child+1 < hi && data[first+child].literal < data[first+child+1].literal { - child++ - } - if data[first+root].literal > data[first+child].literal { - return - } - data[first+root], data[first+child] = data[first+child], data[first+root] - root = child - } -} -func doPivot(data []literalNode, lo, hi int) (midlo, midhi int) { - m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow. - if hi-lo > 40 { - // Tukey's ``Ninther,'' median of three medians of three. - s := (hi - lo) / 8 - medianOfThree(data, lo, lo+s, lo+2*s) - medianOfThree(data, m, m-s, m+s) - medianOfThree(data, hi-1, hi-1-s, hi-1-2*s) - } - medianOfThree(data, lo, m, hi-1) - - // Invariants are: - // data[lo] = pivot (set up by ChoosePivot) - // data[lo < i < a] < pivot - // data[a <= i < b] <= pivot - // data[b <= i < c] unexamined - // data[c <= i < hi-1] > pivot - // data[hi-1] >= pivot - pivot := lo - a, c := lo+1, hi-1 - - for ; a < c && data[a].literal < data[pivot].literal; a++ { - } - b := a - for { - for ; b < c && data[pivot].literal > data[b].literal; b++ { // data[b] <= pivot - } - for ; b < c && data[pivot].literal < data[c-1].literal; c-- { // data[c-1] > pivot - } - if b >= c { - break - } - // data[b] > pivot; data[c-1] <= pivot - data[b], data[c-1] = data[c-1], data[b] - b++ - c-- - } - // If hi-c<3 then there are duplicates (by property of median of nine). - // Let's be a bit more conservative, and set border to 5. - protect := hi-c < 5 - if !protect && hi-c < (hi-lo)/4 { - // Lets test some points for equality to pivot - dups := 0 - if data[pivot].literal > data[hi-1].literal { // data[hi-1] = pivot - data[c], data[hi-1] = data[hi-1], data[c] - c++ - dups++ - } - if data[b-1].literal > data[pivot].literal { // data[b-1] = pivot - b-- - dups++ - } - // m-lo = (hi-lo)/2 > 6 - // b-lo > (hi-lo)*3/4-1 > 8 - // ==> m < b ==> data[m] <= pivot - if data[m].literal > data[pivot].literal { // data[m] = pivot - data[m], data[b-1] = data[b-1], data[m] - b-- - dups++ - } - // if at least 2 points are equal to pivot, assume skewed distribution - protect = dups > 1 - } - if protect { - // Protect against a lot of duplicates - // Add invariant: - // data[a <= i < b] unexamined - // data[b <= i < c] = pivot - for { - for ; a < b && data[b-1].literal > data[pivot].literal; b-- { // data[b] == pivot - } - for ; a < b && data[a].literal < data[pivot].literal; a++ { // data[a] < pivot - } - if a >= b { - break - } - // data[a] == pivot; data[b-1] < pivot - data[a], data[b-1] = data[b-1], data[a] - a++ - b-- - } - } - // Swap pivot into middle - data[pivot], data[b-1] = data[b-1], data[pivot] - return b - 1, c -} - -// Insertion sort -func insertionSort(data []literalNode, a, b int) { - for i := a + 1; i < b; i++ { - for j := i; j > a && data[j].literal < data[j-1].literal; j-- { - data[j], data[j-1] = data[j-1], data[j] - } - } -} - -// maxDepth returns a threshold at which quicksort should switch -// to heapsort. It returns 2*ceil(lg(n+1)). -func maxDepth(n int) int { - var depth int - for i := n; i > 0; i >>= 1 { - depth++ - } - return depth * 2 -} - -// medianOfThree moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. -func medianOfThree(data []literalNode, m1, m0, m2 int) { - // sort 3 elements - if data[m1].literal < data[m0].literal { - data[m1], data[m0] = data[m0], data[m1] - } - // data[m0] <= data[m1] - if data[m2].literal < data[m1].literal { - data[m2], data[m1] = data[m1], data[m2] - // data[m0] <= data[m2] && data[m1] < data[m2] - if data[m1].literal < data[m0].literal { - data[m1], data[m0] = data[m0], data[m1] - } - } - // now data[m0] <= data[m1] <= data[m2] -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/auth/manager/loader/loader.go b/vendor/github.com/owncloud/reva/v2/pkg/auth/manager/loader/loader.go index 843c9bda9ba..9c5714ace3b 100644 --- a/vendor/github.com/owncloud/reva/v2/pkg/auth/manager/loader/loader.go +++ b/vendor/github.com/owncloud/reva/v2/pkg/auth/manager/loader/loader.go @@ -29,7 +29,6 @@ import ( _ "github.com/owncloud/reva/v2/pkg/auth/manager/nextcloud" _ "github.com/owncloud/reva/v2/pkg/auth/manager/ocmshares" _ "github.com/owncloud/reva/v2/pkg/auth/manager/oidc" - _ "github.com/owncloud/reva/v2/pkg/auth/manager/owncloudsql" _ "github.com/owncloud/reva/v2/pkg/auth/manager/publicshares" _ "github.com/owncloud/reva/v2/pkg/auth/manager/serviceaccounts" // Add your own here diff --git a/vendor/github.com/owncloud/reva/v2/pkg/auth/manager/owncloudsql/accounts/accounts.go b/vendor/github.com/owncloud/reva/v2/pkg/auth/manager/owncloudsql/accounts/accounts.go deleted file mode 100644 index 11eb70cefd2..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/auth/manager/owncloudsql/accounts/accounts.go +++ /dev/null @@ -1,165 +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 accounts - -import ( - "context" - "database/sql" - "strings" - "time" - - "github.com/owncloud/reva/v2/pkg/appctx" - "github.com/pkg/errors" -) - -// Accounts represents oc10-style Accounts -type Accounts struct { - driver string - db *sql.DB - joinUsername, joinUUID, enableMedialSearch bool - selectSQL string -} - -// NewMysql returns a new accounts instance connecting to a MySQL database -func NewMysql(dsn string, joinUsername, joinUUID, enableMedialSearch bool) (*Accounts, error) { - sqldb, err := sql.Open("mysql", dsn) - if err != nil { - return nil, errors.Wrap(err, "error connecting to the database") - } - - // FIXME make configurable - sqldb.SetConnMaxLifetime(time.Minute * 3) - sqldb.SetConnMaxIdleTime(time.Second * 30) - sqldb.SetMaxOpenConns(100) - sqldb.SetMaxIdleConns(10) - - err = sqldb.Ping() - if err != nil { - return nil, errors.Wrap(err, "error connecting to the database") - } - - return New("mysql", sqldb, joinUsername, joinUUID, enableMedialSearch) -} - -// New returns a new accounts instance connecting to the given sql.DB -func New(driver string, sqldb *sql.DB, joinUsername, joinUUID, enableMedialSearch bool) (*Accounts, error) { - - sel := "SELECT id, email, user_id, display_name, quota, last_login, backend, home, state, password" - from := ` - FROM oc_accounts a - LEFT JOIN oc_users u - ON a.user_id=u.uid - ` - if joinUsername { - sel += ", p.configvalue AS username" - from += `LEFT JOIN oc_preferences p - ON a.user_id=p.userid - AND p.appid='core' - AND p.configkey='username'` - } else { - // fallback to user_id as username - sel += ", user_id AS username" - } - if joinUUID { - sel += ", p2.configvalue AS ownclouduuid" - from += `LEFT JOIN oc_preferences p2 - ON a.user_id=p2.userid - AND p2.appid='core' - AND p2.configkey='ownclouduuid'` - } else { - // fallback to user_id as ownclouduuid - sel += ", user_id AS ownclouduuid" - } - - return &Accounts{ - driver: driver, - db: sqldb, - joinUsername: joinUsername, - joinUUID: joinUUID, - enableMedialSearch: enableMedialSearch, - selectSQL: sel + from, - }, nil -} - -// Account stores information about accounts. -type Account struct { - ID uint64 - Email sql.NullString - UserID string - DisplayName sql.NullString - Quota sql.NullString - LastLogin int - Backend string - Home string - State int8 - PasswordHash string // from oc_users - Username sql.NullString // optional comes from the oc_preferences - OwnCloudUUID sql.NullString // optional comes from the oc_preferences -} - -func (as *Accounts) rowToAccount(ctx context.Context, row Scannable) (*Account, error) { - a := Account{} - if err := row.Scan(&a.ID, &a.Email, &a.UserID, &a.DisplayName, &a.Quota, &a.LastLogin, &a.Backend, &a.Home, &a.State, &a.PasswordHash, &a.Username, &a.OwnCloudUUID); err != nil { - appctx.GetLogger(ctx).Error().Err(err).Msg("could not scan row, skipping") - return nil, err - } - - return &a, nil -} - -// Scannable describes the interface providing a Scan method -type Scannable interface { - Scan(...interface{}) error -} - -// GetAccountByLogin fetches an account by mail or username -func (as *Accounts) GetAccountByLogin(ctx context.Context, login string) (*Account, error) { - var row *sql.Row - username := strings.ToLower(login) // usernames are lowercased in owncloud classic - if as.joinUsername { - row = as.db.QueryRowContext(ctx, as.selectSQL+" WHERE a.email=? OR a.lower_user_id=? OR p.configvalue=?", login, username, login) - } else { - row = as.db.QueryRowContext(ctx, as.selectSQL+" WHERE a.email=? OR a.lower_user_id=?", login, username) - } - - return as.rowToAccount(ctx, row) -} - -// GetAccountGroups reads the groups for an account -func (as *Accounts) GetAccountGroups(ctx context.Context, uid string) ([]string, error) { - rows, err := as.db.QueryContext(ctx, "SELECT gid FROM oc_group_user WHERE uid=?", uid) - if err != nil { - return nil, err - } - defer rows.Close() - - var group string - groups := []string{} - for rows.Next() { - if err := rows.Scan(&group); err != nil { - appctx.GetLogger(ctx).Error().Err(err).Msg("could not scan row, skipping") - continue - } - groups = append(groups, group) - } - if err = rows.Err(); err != nil { - return nil, err - } - return groups, nil -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/auth/manager/owncloudsql/accounts/test.sqlite b/vendor/github.com/owncloud/reva/v2/pkg/auth/manager/owncloudsql/accounts/test.sqlite deleted file mode 100644 index c68bb753774fc28eda95f71768f5d41d29830363..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90112 zcmeI*T~8a?8Nl%wFc_S`u)86nYK1ZisfZN_HUtP+Z8t!i4O+rW78;0FGJ`!hPHc}e zV=pi54QZwQ2z$|s_F~n3fJ&8C>X&G*Dpl%brD|2FdzIUsIm6gvjDc!ZwOIcVG4pcH z%yWKo=FFTiGx>aNDPOUSUE3*|6=PBvP*hcUXc&s3OpAZl#lK+qjTngp6XLfT8gDf^ zt=vsK=-jK5vNqxD8 zBGxQIh`ldJ;_A^za|mX(m@lRMO#ZmYB4k5zJ~$(eQ?s9lMW(3-)B9dj|7`3;FvnFR zU9p^^D?e)>4U67CHI{}Xq1?4V8WMP+c-s*$9}YzI=OUI*!Y~bqgG!qY?V^SR?_D6G zAVaW@)^XKSis)ARqx#U0`qr(OI|WPZ6tBLx!Efz&!|QYNODSVJn7wV>jv0pB@y3^? zlX+=6x2NyLjpdbfV|im~$#|4{Jh!p5Zj6k?Mn%Ho+Ugw-+YK3lbY3QzvTVBUp6z7o z$tI?zTCRBY+2Yf=XIsW+sjZ+M#^U2zx4tl>Dw5lMQxMk^X|r0f<+RsC$P%WL!Pvmt zTUVocJg)w-GcYJS)~@APrHtiCgr@qMJVFZt6#*!Yh=y{uf-%eGaMH<%mPOsp*rnZk z?zMGru{Hb2zcdR~L0+&+xr()4sqL4*`vjmL`RKME)@W#(@mTz$zHWUbeuis^Lh6R5 z`wwGL{l*RTXdxh$$G)fQk6dYj_2(a(=E6#6)U?L(c)3L2$t&G@>c$y?`>Izh~GqWAQ;<+A@U_6r|Q&4nEG+u&Gywzpe_yuH&{WZAOT$}Fd%Eeb6c{trt1QS_XeDfenxh8HTfobLI>&pM;}@UZ&T$3eM| z+efc*2BVRt5(i6;i|f_Te5qpPEXSxfgW)|jx3RvmxGdK5G_|}g+dBVZqSj8xhOVhW zl&9}-?Qdug<$4PCo;bx{AYsEr!#Uf1Q?)B*y{Yk9rl!~mrdvrD>|DN7FMLx=myj{; zn3>mBsYRKWB6L2H3B(zyBHFS*`ScBKEHAEYq`Z@XciAJ4e{lk;&26L- zZNpSox4t-h#sL%T=k!FVI&@D(E}G3d%>?V{5*N@t-bM6kN4I|Gj=Cjl-0>;Owcb?a z8OJ|k1(Tf(HSa{)+uj*J zFd%>c0tg_000IagfB*srAb>!d2=uF+!@c!CKXCp2|2K;EO`8;uVjzG30tg_000Iag zfB*srAaF?qUZ_`;ab4Gc6d9Z8l@HN8wD!$nxnSM3ot&Ie|41eh#;5b!M<1`OEPSyc z9?JOQktrT~FkNfBSgMR?#l-k-zF@iIGEYpHsMi(Ss`v2{c|9_kAT#AJN0tg_000IagfB*srAb`NJfdBr#?-lKPNDx2(0R#|0 z009ILKmY**5I~@91p3rYU9Z0`(0~4){(sx-UkZW%0tg_000IagfB*srAb@};&>zu{ zU;QU<0{Gwm^ZtJ-2LcEnfB*srAb#A$@}K)y_w16J+Z-|@J1WPhdR3S_@E|NzMFT%Caa~r z{GsV&-Iw-WVpwequ#zuYhjz(IRUNx*jn7+7Az$*o|Bq;2E85rEKeRt-zi-$869^!H z00IagfB*srAbGt{eM+^C!YUrorWa{Abhash($message); - switch len(hash) { - case 60: // legacy PHPass hash - return nil == bcrypt.CompareHashAndPassword([]byte(hash), []byte(password+m.c.LegacySalt)) - case 40: // legacy sha1 hash - h := sha1.Sum([]byte(password)) - return hmac.Equal([]byte(hash), []byte(hex.EncodeToString(h[:]))) - } - return false -} -func (m *manager) verifyHashV1(password, hash string) bool { - // TODO implement password_needs_rehash - return nil == bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/loader/loader.go b/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/loader/loader.go index cc92c0e9670..d39d39b636e 100644 --- a/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/loader/loader.go +++ b/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/loader/loader.go @@ -23,6 +23,5 @@ import ( _ "github.com/owncloud/reva/v2/pkg/publicshare/manager/cs3" _ "github.com/owncloud/reva/v2/pkg/publicshare/manager/json" _ "github.com/owncloud/reva/v2/pkg/publicshare/manager/memory" - _ "github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql" // Add your own here ) diff --git a/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/conversions.go b/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/conversions.go deleted file mode 100644 index 84abf766003..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/conversions.go +++ /dev/null @@ -1,226 +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 owncloudsql - -import ( - "context" - "time" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/owncloud/reva/v2/pkg/conversions" - "github.com/owncloud/reva/v2/pkg/rgrpc/status" - "github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool" - "github.com/jellydator/ttlcache/v2" -) - -// DBShare stores information about user and public shares. -type DBShare struct { - ID string - UIDOwner string - UIDInitiator string - ItemStorage string - FileSource string - ItemType string // 'file' or 'folder' - ShareWith string - Token string - Expiration string - Permissions int - ShareType int - ShareName string - STime int - FileTarget string - RejectedBy string - State int - Parent int -} - -// UserConverter describes an interface for converting user ids to names and back -type UserConverter interface { - UserNameToUserID(ctx context.Context, username string) (*userpb.UserId, error) - UserIDToUserName(ctx context.Context, userid *userpb.UserId) (string, error) -} - -// GatewayUserConverter converts usernames and ids using the gateway -type GatewayUserConverter struct { - gwAddr string - - IDCache *ttlcache.Cache - NameCache *ttlcache.Cache -} - -// NewGatewayUserConverter returns a instance of GatewayUserConverter -func NewGatewayUserConverter(gwAddr string) *GatewayUserConverter { - IDCache := ttlcache.NewCache() - _ = IDCache.SetTTL(30 * time.Second) - IDCache.SkipTTLExtensionOnHit(true) - NameCache := ttlcache.NewCache() - _ = NameCache.SetTTL(30 * time.Second) - NameCache.SkipTTLExtensionOnHit(true) - - return &GatewayUserConverter{ - gwAddr: gwAddr, - IDCache: IDCache, - NameCache: NameCache, - } -} - -// UserIDToUserName converts a user ID to an username -func (c *GatewayUserConverter) UserIDToUserName(ctx context.Context, userid *userpb.UserId) (string, error) { - username, err := c.NameCache.Get(userid.String()) - if err == nil { - return username.(string), nil - } - - gwConn, err := pool.GetGatewayServiceClient(c.gwAddr) - if err != nil { - return "", err - } - getUserResponse, err := gwConn.GetUser(ctx, &userpb.GetUserRequest{ - UserId: userid, - SkipFetchingUserGroups: true, - }) - if err != nil { - return "", err - } - if getUserResponse.Status.Code != rpc.Code_CODE_OK { - return "", status.NewErrorFromCode(getUserResponse.Status.Code, "gateway") - } - _ = c.NameCache.Set(userid.String(), getUserResponse.User.Username) - return getUserResponse.User.Username, nil -} - -// UserNameToUserID converts a username to an user ID -func (c *GatewayUserConverter) UserNameToUserID(ctx context.Context, username string) (*userpb.UserId, error) { - id, err := c.IDCache.Get(username) - if err == nil { - return id.(*userpb.UserId), nil - } - - gwConn, err := pool.GetGatewayServiceClient(c.gwAddr) - if err != nil { - return nil, err - } - getUserResponse, err := gwConn.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ - Claim: "username", - Value: username, - SkipFetchingUserGroups: true, - }) - if err != nil { - return nil, err - } - if getUserResponse.Status.Code != rpc.Code_CODE_OK { - return nil, status.NewErrorFromCode(getUserResponse.Status.Code, "gateway") - } - _ = c.IDCache.Set(username, getUserResponse.User.Id) - return getUserResponse.User.Id, nil -} - -func resourceTypeToItem(r provider.ResourceType) string { - switch r { - case provider.ResourceType_RESOURCE_TYPE_FILE: - return "file" - case provider.ResourceType_RESOURCE_TYPE_CONTAINER: - return "folder" - case provider.ResourceType_RESOURCE_TYPE_REFERENCE: - return "reference" - case provider.ResourceType_RESOURCE_TYPE_SYMLINK: - return "symlink" - default: - return "" - } -} - -func sharePermToInt(p *provider.ResourcePermissions) int { - return int(conversions.RoleFromResourcePermissions(p, true).OCSPermissions()) -} - -func intTosharePerm(p int) (*provider.ResourcePermissions, error) { - perms, err := conversions.NewPermissions(p) - if err != nil { - return nil, err - } - - return conversions.RoleFromOCSPermissions(perms, nil).CS3ResourcePermissions(), nil -} - -func formatUserID(u *userpb.UserId) string { - return u.OpaqueId -} - -// ConvertToCS3PublicShare converts a DBShare to a CS3API public share -func (m *mgr) ConvertToCS3PublicShare(ctx context.Context, s DBShare) (*link.PublicShare, error) { - ts := &typespb.Timestamp{ - Seconds: uint64(s.STime), - } - permissions, err := intTosharePerm(s.Permissions) - if err != nil { - return nil, err - } - owner, err := m.userConverter.UserNameToUserID(ctx, s.UIDOwner) - if err != nil { - return nil, err - } - var creator *userpb.UserId - if s.UIDOwner == s.UIDInitiator { - creator = owner - } else { - creator, err = m.userConverter.UserNameToUserID(ctx, s.UIDOwner) - if err != nil { - return nil, err - } - } - pwd := false - if s.ShareWith != "" { - pwd = true - } - var expires *typespb.Timestamp - if s.Expiration != "" { - t, err := time.Parse("2006-01-02 15:04:05", s.Expiration) - if err != nil { - t, err = time.Parse("2006-01-02 15:04:05-07:00", s.Expiration) - } - if err == nil { - expires = &typespb.Timestamp{ - Seconds: uint64(t.Unix()), - } - } - } - return &link.PublicShare{ - Id: &link.PublicShareId{ - OpaqueId: s.ID, - }, - ResourceId: &provider.ResourceId{ - SpaceId: s.ItemStorage, - OpaqueId: s.FileSource, - }, - Permissions: &link.PublicSharePermissions{Permissions: permissions}, - Owner: owner, - Creator: creator, - Token: s.Token, - DisplayName: s.ShareName, - PasswordProtected: pwd, - Expiration: expires, - Ctime: ts, - Mtime: ts, - }, nil -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/owncloudsql.go b/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/owncloudsql.go deleted file mode 100644 index ad9de7aa6fd..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/owncloudsql.go +++ /dev/null @@ -1,533 +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 owncloudsql implements a publiclink share manager backed by an existing ownCloud 10 database -// -// The SQL queries use `coalesce({column_identifier}, ”) as {column_identifier}` to read an emptystring -// instead of null values, which better fits the golang default values. -package owncloudsql - -import ( - "context" - "database/sql" - "fmt" - "strconv" - "strings" - "time" - - user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/owncloud/reva/v2/pkg/errtypes" - "github.com/owncloud/reva/v2/pkg/publicshare" - "github.com/owncloud/reva/v2/pkg/publicshare/manager/registry" - "github.com/owncloud/reva/v2/pkg/sharedconf" - "github.com/owncloud/reva/v2/pkg/utils" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" - "golang.org/x/crypto/bcrypt" - "google.golang.org/protobuf/proto" - - // Provides mysql drivers - _ "github.com/go-sql-driver/mysql" -) - -const ( - publicShareType = 3 -) - -func init() { - registry.Register("owncloudsql", NewMysql) -} - -// Config configures an owncloudsql publicshare manager -type Config struct { - GatewayAddr string `mapstructure:"gateway_addr"` - DbUsername string `mapstructure:"db_username"` - DbPassword string `mapstructure:"db_password"` - DbHost string `mapstructure:"db_host"` - DbPort int `mapstructure:"db_port"` - DbName string `mapstructure:"db_name"` - EnableExpiredSharesCleanup bool `mapstructure:"enable_expired_shares_cleanup"` - SharePasswordHashCost int `mapstructure:"password_hash_cost"` -} - -type mgr struct { - driver string - db *sql.DB - c Config - userConverter UserConverter -} - -// NewMysql returns a new publicshare manager connection to a mysql database -func NewMysql(m map[string]interface{}) (publicshare.Manager, error) { - c, err := parseConfig(m) - if err != nil { - err = errors.Wrap(err, "error creating a new manager") - return nil, err - } - - db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DbUsername, c.DbPassword, c.DbHost, c.DbPort, c.DbName)) - if err != nil { - return nil, err - } - - userConverter := NewGatewayUserConverter(sharedconf.GetGatewaySVC(c.GatewayAddr)) - - return New("mysql", db, *c, userConverter) -} - -// New returns a new Cache instance connecting to the given sql.DB -func New(driver string, db *sql.DB, c Config, userConverter UserConverter) (publicshare.Manager, error) { - if c.SharePasswordHashCost == 0 { - c.SharePasswordHashCost = bcrypt.DefaultCost - } - return &mgr{ - driver: driver, - db: db, - c: c, - userConverter: userConverter, - }, nil -} - -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 (m *mgr) CreatePublicShare(ctx context.Context, u *user.User, rInfo *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) { - - tkn := utils.RandString(15) - now := time.Now().Unix() - - displayName := tkn - if rInfo.ArbitraryMetadata != nil && rInfo.ArbitraryMetadata.Metadata["name"] != "" { - displayName = rInfo.ArbitraryMetadata.Metadata["name"] - } - createdAt := &typespb.Timestamp{ - Seconds: uint64(now), - } - - creator := u.Username - owner, err := m.userConverter.UserIDToUserName(ctx, rInfo.Owner) - if err != nil { - return nil, err - } - permissions := sharePermToInt(g.Permissions.Permissions) - itemType := resourceTypeToItem(rInfo.Type) - - itemSource := rInfo.Id.OpaqueId - fileSource, err := strconv.ParseUint(itemSource, 10, 64) - if err != nil { - // it can be the case that the item source may be a character string - // we leave fileSource blank in that case - fileSource = 0 - } - - columns := "share_type,uid_owner,uid_initiator,item_type,item_source,file_source,permissions,stime,token,share_name" - placeholders := "?,?,?,?,?,?,?,?,?,?" - params := []interface{}{publicShareType, owner, creator, itemType, itemSource, fileSource, permissions, now, tkn, displayName} - - var passwordProtected bool - password := g.Password - if password != "" { - password, err = hashPassword(password, m.c.SharePasswordHashCost) - if err != nil { - return nil, errors.Wrap(err, "could not hash share password") - } - passwordProtected = true - - columns += ",share_with" - placeholders += ",?" - params = append(params, password) - } - - if g.Expiration != nil && g.Expiration.Seconds != 0 { - t := time.Unix(int64(g.Expiration.Seconds), 0) - columns += ",expiration" - placeholders += ",?" - params = append(params, t) - } - - query := "INSERT INTO oc_share (" + columns + ") VALUES (" + placeholders + ")" - stmt, err := m.db.Prepare(query) - if err != nil { - return nil, err - } - result, err := stmt.Exec(params...) - if err != nil { - return nil, err - } - lastID, err := result.LastInsertId() - if err != nil { - return nil, err - } - - return &link.PublicShare{ - Id: &link.PublicShareId{ - OpaqueId: strconv.FormatInt(lastID, 10), - }, - Owner: rInfo.GetOwner(), - Creator: u.Id, - ResourceId: rInfo.Id, - Token: tkn, - Permissions: g.Permissions, - Ctime: createdAt, - Mtime: createdAt, - PasswordProtected: passwordProtected, - Expiration: g.Expiration, - DisplayName: displayName, - }, nil -} - -// owncloud 10 prefixes the hash with `1|` -func hashPassword(password string, cost int) (string, error) { - bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost) - return "1|" + string(bytes), err -} - -// UpdatePublicShare updates the expiration date, permissions and Mtime -func (m *mgr) UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest) (*link.PublicShare, error) { - query := "update oc_share set " - paramsMap := map[string]interface{}{} - params := []interface{}{} - - now := time.Now().Unix() - uid := u.Username - - switch req.GetUpdate().GetType() { - case link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME: - paramsMap["share_name"] = req.Update.GetDisplayName() - case link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS: - paramsMap["permissions"] = sharePermToInt(req.Update.GetGrant().GetPermissions().Permissions) - case link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION: - paramsMap["expiration"] = time.Unix(int64(req.Update.GetGrant().Expiration.Seconds), 0) - case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD: - if req.Update.GetGrant().Password == "" { - paramsMap["share_with"] = "" - } else { - h, err := hashPassword(req.Update.GetGrant().Password, m.c.SharePasswordHashCost) - if err != nil { - return nil, errors.Wrap(err, "could not hash share password") - } - paramsMap["share_with"] = h - } - default: - return nil, fmt.Errorf("invalid update type: %v", req.GetUpdate().GetType()) - } - - for k, v := range paramsMap { - query += k + "=?" - params = append(params, v) - } - - switch { - case req.Ref.GetId() != nil: - query += ",stime=? where id=? AND (uid_owner=? or uid_initiator=?)" - params = append(params, now, req.Ref.GetId().OpaqueId, uid, uid) - case req.Ref.GetToken() != "": - query += ",stime=? where token=? AND (uid_owner=? or uid_initiator=?)" - params = append(params, now, req.Ref.GetToken(), uid, uid) - default: - return nil, errtypes.NotFound(req.Ref.String()) - } - - stmt, err := m.db.Prepare(query) - if err != nil { - return nil, err - } - if _, err = stmt.Exec(params...); err != nil { - return nil, err - } - - return m.GetPublicShare(ctx, u, req.Ref, false) -} - -func (m *mgr) GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference, sign bool) (share *link.PublicShare, err error) { - - ps, err := m.getWithPassword(ctx, ref) - if err != nil { - return nil, err - } - - if publicshare.IsExpired(&ps.PublicShare) { - if err := m.cleanupExpiredShares(); err != nil { - return nil, err - } - return nil, errtypes.NotFound("public share has expired") - } - - if ps.PublicShare.PasswordProtected && sign { - err = publicshare.AddSignature(&ps.PublicShare, ps.Password) - if err != nil { - return nil, err - } - } - - return &ps.PublicShare, nil -} - -func (m *mgr) getWithPassword(ctx context.Context, ref *link.PublicShareReference) (*publicshare.WithPassword, error) { - switch { - case ref.GetToken() != "": - return m.getByToken(ctx, ref.GetToken()) - case ref.GetId().GetOpaqueId() != "": - return m.getByID(ctx, ref.GetId().GetOpaqueId()) - default: - return nil, errtypes.BadRequest("neither id nor token given") - } -} - -func (m *mgr) getByToken(ctx context.Context, token string) (*publicshare.WithPassword, error) { - s, err := getByToken(m.db, token) - if err != nil { - return nil, err - } - ps, err := m.ConvertToCS3PublicShare(ctx, s) - if err != nil { - return nil, err - } - ret := &publicshare.WithPassword{ - Password: strings.TrimPrefix(s.ShareWith, "1|"), - } - proto.Merge(&ret.PublicShare, ps) - return ret, nil -} - -func getByToken(db *sql.DB, token string) (DBShare, error) { - s := DBShare{Token: token} - query := `SELECT - coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, - coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, - coalesce(item_type, '') as item_type, - coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, - s.id, s.stime, s.permissions, fc.storage as storage - FROM oc_share s - LEFT JOIN oc_filecache fc ON fc.fileid = file_source - WHERE share_type=? AND token=?` - if err := db.QueryRow(query, publicShareType, token).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.ItemType, &s.Expiration, &s.ShareName, &s.ID, &s.STime, &s.Permissions, &s.ItemStorage); err != nil { - if err == sql.ErrNoRows { - return s, errtypes.NotFound(token) - } - return s, err - } - return s, nil -} - -func (m *mgr) getByID(ctx context.Context, id string) (*publicshare.WithPassword, error) { - s := DBShare{ID: id} - query := `SELECT - coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, - coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, - coalesce(item_type, '') as item_type, coalesce(token,'') as token, - coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, - s.stime, s.permissions, fc.storage as storage - FROM oc_share s - LEFT JOIN oc_filecache fc ON fc.fileid = file_source - WHERE share_type=? AND id=?` - if err := m.db.QueryRow(query, publicShareType, id).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.ItemType, &s.Token, &s.Expiration, &s.ShareName, &s.STime, &s.Permissions, &s.ItemStorage); err != nil { - if err == sql.ErrNoRows { - return nil, errtypes.NotFound(id) - } - return nil, err - } - ps, err := m.ConvertToCS3PublicShare(ctx, s) - if err != nil { - return nil, err - } - ret := &publicshare.WithPassword{ - Password: strings.TrimPrefix(s.ShareWith, "1|"), - } - proto.Merge(&ret.PublicShare, ps) - return ret, nil -} - -func (m *mgr) ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, sign bool) ([]*link.PublicShare, error) { - uid := u.Username - // FIXME instead of joining we may want to have to do a stat call ... if we want to store shares from other providers? or just Dump()? and be done with migration? - query := `SELECT - coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, - coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, - coalesce(item_type, '') as item_type, coalesce(token,'') as token, - coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, - s.id, s.stime, s.permissions, fc.storage as storage - FROM oc_share s - LEFT JOIN oc_filecache fc ON fc.fileid = file_source - WHERE (uid_owner=? or uid_initiator=?) - AND (share_type=?)` - var resourceFilters, ownerFilters, creatorFilters, storageFilters string - var resourceParams, ownerParams, creatorParams, storageParams []interface{} - params := []interface{}{uid, uid, publicShareType} - - for _, f := range filters { - switch f.Type { - case link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID: - if len(resourceFilters) != 0 { - resourceFilters += " OR " - } - resourceFilters += "item_source=?" - resourceParams = append(resourceParams, f.GetResourceId().GetOpaqueId()) - case link.ListPublicSharesRequest_Filter_TYPE_OWNER: - if len(ownerFilters) != 0 { - ownerFilters += " OR " - } - ownerFilters += "(uid_owner=?)" - ownerParams = append(ownerParams, formatUserID(f.GetOwner())) - case link.ListPublicSharesRequest_Filter_TYPE_CREATOR: - if len(creatorFilters) != 0 { - creatorFilters += " OR " - } - creatorFilters += "(uid_initiator=?)" - creatorParams = append(creatorParams, formatUserID(f.GetCreator())) - case publicshare.StorageIDFilterType: - if len(storageFilters) != 0 { - storageFilters += " OR " - } - storageFilters += "(storage=?)" - storageParams = append(storageParams, f.GetResourceId().GetStorageId()) - } - } - if resourceFilters != "" { - query = fmt.Sprintf("%s AND (%s)", query, resourceFilters) - params = append(params, resourceParams...) - } - if ownerFilters != "" { - query = fmt.Sprintf("%s AND (%s)", query, ownerFilters) - params = append(params, ownerParams...) - } - if creatorFilters != "" { - query = fmt.Sprintf("%s AND (%s)", query, creatorFilters) - params = append(params, creatorParams...) - } - if storageFilters != "" { - query = fmt.Sprintf("%s AND (%s)", query, storageFilters) - params = append(params, storageParams...) - } - - rows, err := m.db.Query(query, params...) - if err != nil { - return nil, err - } - defer rows.Close() - - var s DBShare - shares := []*link.PublicShare{} - for rows.Next() { - if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.ItemType, &s.Token, &s.Expiration, &s.ShareName, &s.ID, &s.STime, &s.Permissions, &s.ItemStorage); err != nil { - continue - } - var cs3Share *link.PublicShare - if cs3Share, err = m.ConvertToCS3PublicShare(ctx, s); err != nil { - return nil, err - } - if publicshare.IsExpired(cs3Share) { - _ = m.cleanupExpiredShares() - } else { - if cs3Share.PasswordProtected && sign { - if err := publicshare.AddSignature(cs3Share, strings.TrimPrefix(s.ShareWith, "1|")); err != nil { - return nil, err - } - } - shares = append(shares, cs3Share) - } - } - if err = rows.Err(); err != nil { - return nil, err - } - - return shares, nil -} - -func (m *mgr) RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error { - uid := u.Username - query := "delete from oc_share where " - params := []interface{}{} - - switch { - case ref.GetId() != nil && ref.GetId().OpaqueId != "": - query += "id=? AND (uid_owner=? or uid_initiator=?)" - params = append(params, ref.GetId().OpaqueId, uid, uid) - case ref.GetToken() != "": - query += "token=? AND (uid_owner=? or uid_initiator=?)" - params = append(params, ref.GetToken(), uid, uid) - default: - return errtypes.NotFound(ref.String()) - } - - stmt, err := m.db.Prepare(query) - if err != nil { - return err - } - res, err := stmt.Exec(params...) - if err != nil { - return err - } - - rowCnt, err := res.RowsAffected() - if err != nil { - return err - } - if rowCnt == 0 { - return errtypes.NotFound(ref.String()) - } - return nil -} - -func (m *mgr) GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error) { - ps, err := m.getByToken(ctx, token) - if err != nil { - return nil, err - } - - if publicshare.IsExpired(&ps.PublicShare) { - if err := m.cleanupExpiredShares(); err != nil { - return nil, err - } - return nil, errtypes.NotFound("public share has expired") - } - - if ps.PublicShare.PasswordProtected { - if !publicshare.Authenticate(&ps.PublicShare, ps.Password, auth) { - return nil, errtypes.InvalidCredentials("access denied") - } - } - - return &ps.PublicShare, nil -} - -func (m *mgr) cleanupExpiredShares() error { - if !m.c.EnableExpiredSharesCleanup { - return nil - } - - query := "DELETE FROM oc_share WHERE expiration IS NOT NULL AND expiration < ?" - params := []interface{}{time.Now().Format("2006-01-02 03:04:05")} - - stmt, err := m.db.Prepare(query) - if err != nil { - return err - } - if _, err = stmt.Exec(params...); err != nil { - return err - } - return nil -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/test.db b/vendor/github.com/owncloud/reva/v2/pkg/publicshare/manager/owncloudsql/test.db deleted file mode 100644 index fba76fdcc4fc7816bf2b281f9c2074bf1955842c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 655360 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 diff --git a/vendor/github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool/selector.go b/vendor/github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool/selector.go index eb364130955..27fba088c37 100644 --- a/vendor/github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool/selector.go +++ b/vendor/github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool/selector.go @@ -20,6 +20,7 @@ package pool import ( "fmt" + "math/rand" "strings" "sync" @@ -45,7 +46,9 @@ import ( "github.com/owncloud/reva/v2/pkg/registry" "github.com/pkg/errors" "github.com/sercand/kuberesolver/v5" + mRegistry "go-micro.dev/v4/registry" "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" ) func init() { @@ -82,6 +85,27 @@ func GetSelector[T any](k string, id string, f func(cc grpc.ClientConnInterface) return newSelector } +// cachedClient pairs a grpc client with the connection backing it, so Next +// can check the connection's health before handing the client back out. +type cachedClient[T any] struct { + conn *grpc.ClientConn + client T +} + +// healthy reports whether the cached connection is safe to hand out again. +// A connection only becomes TransientFailure or Shutdown after a real RPC on +// it has already failed - a connection that was never used for an RPC stays +// Idle, so it is treated as healthy here (there is no way to probe +// reachability without using it). +func (c cachedClient[T]) healthy() bool { + switch c.conn.GetState() { + case connectivity.TransientFailure, connectivity.Connecting, connectivity.Shutdown: + return false + default: + return true + } +} + type Selector[T any] struct { id string clientFactory func(cc grpc.ClientConnInterface) T @@ -101,47 +125,99 @@ func (s *Selector[T]) Next(opts ...Option) (T, error) { opt(&options) } - target := s.id // if the target is given as a recognized gRPC URI, skip registry lookup // see https://github.com/grpc/grpc/blob/master/doc/naming.md#name-syntax prefix := strings.SplitN(s.id, ":", 2)[0] switch { - case prefix == "dns": - fallthrough - case prefix == "unix": - fallthrough - case prefix == "kubernetes": - // use target as is and skip registry lookup + case prefix == "dns", prefix == "unix", prefix == "kubernetes": + // use the id as is and skip registry lookup + return s.loadOrNewClient(s.id, allOpts) case options.registry != nil: - // use service registry to look up address + // use the service registry to look up every known node, and pick + // the first one that is ready services, err := options.registry.GetService(s.id) if err != nil { return *new(T), fmt.Errorf("%s: %w", s.id, err) } - nodeAddress, err := registry.GetNodeAddress(services) - if err != nil { - return *new(T), fmt.Errorf("%s: %w", s.id, err) - } - target = nodeAddress + targets := shuffledTargets(services) + return s.nextHealthy(targets, allOpts) default: - // if no registry is available, use the target as is + // if no registry is available, use the id as is + return s.loadOrNewClient(s.id, allOpts) + } +} + +// nextHealthy walks targets in the given order. It returns the client for +// the first target that is ready. A target with no cache entry is also a +// candidate, since Next has no way to know its state without dialing it. If +// no target is ready, nextHealthy reuses the first one found, so the caller +// gets a real error from a real attempt instead of a fabricated one. +func (s *Selector[T]) nextHealthy(targets []string, opts []Option) (T, error) { + if len(targets) == 0 { + return *new(T), fmt.Errorf("%s: no nodes found in registry", s.id) + } + var fallback *cachedClient[T] + for _, target := range targets { + cached, ok := s.clientMap.Load(target) + if !ok { + return s.newClient(target, opts) + } + + cc := cached.(cachedClient[T]) + if cc.healthy() { + return cc.client, nil + } + if fallback == nil { + fallback = &cc + } } - existingClient, ok := s.clientMap.Load(target) - if ok { - return existingClient.(T), nil - } + return fallback.client, nil +} - conn, err := NewConn(target, allOpts...) +// loadOrNewClient returns the cached client for target, creating a new one if +// none exists yet. It does not check health: target was not looked up +// through the registry, so there is no alternative node to fall back to. +func (s *Selector[T]) loadOrNewClient(target string, opts []Option) (T, error) { + if cached, ok := s.clientMap.Load(target); ok { + return cached.(cachedClient[T]).client, nil + } + return s.newClient(target, opts) +} + +// newClient creates a new connection to target, wraps it in a client and caches +// both under target. +func (s *Selector[T]) newClient(target string, opts []Option) (T, error) { + conn, err := NewConn(target, opts...) if err != nil { return *new(T), errors.Wrap(err, fmt.Sprintf("could not create connection for %s to %s", s.id, target)) } - newClient := s.clientFactory(conn) - s.clientMap.Store(target, newClient) + newClient := cachedClient[T]{conn: conn, client: s.clientFactory(conn)} + // if another goroutine already created a client for this target, use it instead + if existing, loaded := s.clientMap.LoadOrStore(target, newClient); loaded { + _ = conn.Close() + return existing.(cachedClient[T]).client, nil + } - return newClient, nil + return newClient.client, nil +} + +// shuffledTargets flattens every node address out of services and returns +// them in random order, so repeated calls spread load across all known +// nodes instead of always preferring the same one. +func shuffledTargets(services []*mRegistry.Service) []string { + var targets []string + for _, svc := range services { + for _, node := range svc.Nodes { + targets = append(targets, node.Address) + } + } + rand.Shuffle(len(targets), func(i, j int) { + targets[i], targets[j] = targets[j], targets[i] + }) + return targets } // GatewaySelector returns a Selector[gateway.GatewayAPIClient]. diff --git a/vendor/github.com/owncloud/reva/v2/pkg/share/manager/loader/loader.go b/vendor/github.com/owncloud/reva/v2/pkg/share/manager/loader/loader.go index c8ae078eb73..6961bef5adb 100644 --- a/vendor/github.com/owncloud/reva/v2/pkg/share/manager/loader/loader.go +++ b/vendor/github.com/owncloud/reva/v2/pkg/share/manager/loader/loader.go @@ -24,6 +24,5 @@ import ( _ "github.com/owncloud/reva/v2/pkg/share/manager/json" _ "github.com/owncloud/reva/v2/pkg/share/manager/jsoncs3" _ "github.com/owncloud/reva/v2/pkg/share/manager/memory" - _ "github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql" // Add your own here ) diff --git a/vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/conversions.go b/vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/conversions.go deleted file mode 100644 index 3a96b4d2b16..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/conversions.go +++ /dev/null @@ -1,301 +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 owncloudsql - -import ( - "context" - "strings" - "time" - - grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - userprovider "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" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/owncloud/reva/v2/pkg/conversions" - "github.com/owncloud/reva/v2/pkg/rgrpc/status" - "github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool" - "github.com/owncloud/reva/v2/pkg/utils" - "github.com/jellydator/ttlcache/v2" -) - -// DBShare stores information about user and public shares. -type DBShare struct { - ID string - UIDOwner string - UIDInitiator string - ItemStorage string - FileSource string - ShareWith string - Token string - Expiration string - Permissions int - ShareType int - ShareName string - STime int - FileTarget string - RejectedBy string - State int - Parent int -} - -// UserConverter describes an interface for converting user ids to names and back -type UserConverter interface { - UserNameToUserID(ctx context.Context, username string) (*userpb.UserId, error) - UserIDToUserName(ctx context.Context, userid *userpb.UserId) (string, error) - GetUser(userid *userpb.UserId) (*userpb.User, error) -} - -// GatewayUserConverter converts usernames and ids using the gateway -type GatewayUserConverter struct { - gwAddr string - - IDCache *ttlcache.Cache - NameCache *ttlcache.Cache -} - -// NewGatewayUserConverter returns a instance of GatewayUserConverter -func NewGatewayUserConverter(gwAddr string) *GatewayUserConverter { - IDCache := ttlcache.NewCache() - _ = IDCache.SetTTL(30 * time.Second) - IDCache.SkipTTLExtensionOnHit(true) - NameCache := ttlcache.NewCache() - _ = NameCache.SetTTL(30 * time.Second) - NameCache.SkipTTLExtensionOnHit(true) - - return &GatewayUserConverter{ - gwAddr: gwAddr, - IDCache: IDCache, - NameCache: NameCache, - } -} - -// UserIDToUserName converts a user ID to an username -func (c *GatewayUserConverter) UserIDToUserName(ctx context.Context, userid *userpb.UserId) (string, error) { - username, err := c.NameCache.Get(userid.String()) - if err == nil { - return username.(string), nil - } - - gwConn, err := pool.GetGatewayServiceClient(c.gwAddr) - if err != nil { - return "", err - } - getUserResponse, err := gwConn.GetUser(ctx, &userprovider.GetUserRequest{ - UserId: userid, - SkipFetchingUserGroups: true, - }) - if err != nil { - return "", err - } - if getUserResponse.Status.Code != rpc.Code_CODE_OK { - return "", status.NewErrorFromCode(getUserResponse.Status.Code, "gateway") - } - _ = c.NameCache.Set(userid.String(), getUserResponse.User.Username) - return getUserResponse.User.Username, nil -} - -// UserNameToUserID converts a username to an user ID -func (c *GatewayUserConverter) UserNameToUserID(ctx context.Context, username string) (*userpb.UserId, error) { - id, err := c.IDCache.Get(username) - if err == nil { - return id.(*userpb.UserId), nil - } - - gwConn, err := pool.GetGatewayServiceClient(c.gwAddr) - if err != nil { - return nil, err - } - getUserResponse, err := gwConn.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ - Claim: "username", - Value: username, - SkipFetchingUserGroups: true, - }) - if err != nil { - return nil, err - } - if getUserResponse.Status.Code != rpc.Code_CODE_OK { - return nil, status.NewErrorFromCode(getUserResponse.Status.Code, "gateway") - } - _ = c.IDCache.Set(username, getUserResponse.User.Id) - return getUserResponse.User.Id, nil -} - -// GetUser gets the user -func (c *GatewayUserConverter) GetUser(userid *userpb.UserId) (*userpb.User, error) { - gwc, err := pool.GetGatewayServiceClient(c.gwAddr) - if err != nil { - return nil, err - } - return utils.GetUser(userid, gwc) -} - -func (m *mgr) formatGrantee(ctx context.Context, g *provider.Grantee) (int, string, error) { - var granteeType int - var formattedID string - switch g.Type { - case provider.GranteeType_GRANTEE_TYPE_USER: - granteeType = 0 - var err error - formattedID, err = m.userConverter.UserIDToUserName(ctx, g.GetUserId()) - if err != nil { - return 0, "", err - } - case provider.GranteeType_GRANTEE_TYPE_GROUP: - granteeType = 1 - formattedID = formatGroupID(g.GetGroupId()) - default: - granteeType = -1 - } - return granteeType, formattedID, nil -} - -func (m *mgr) extractGrantee(ctx context.Context, t int, g string) (*provider.Grantee, error) { - var grantee provider.Grantee - switch t { - case 0: - userid, err := m.userConverter.UserNameToUserID(ctx, g) - if err != nil { - return nil, err - } - grantee.Type = provider.GranteeType_GRANTEE_TYPE_USER - grantee.Id = &provider.Grantee_UserId{UserId: userid} - case 1, 2: - grantee.Type = provider.GranteeType_GRANTEE_TYPE_GROUP - grantee.Id = &provider.Grantee_GroupId{GroupId: extractGroupID(g)} - default: - grantee.Type = provider.GranteeType_GRANTEE_TYPE_INVALID - } - return &grantee, nil -} - -func resourceTypeToItem(r provider.ResourceType) string { - switch r { - case provider.ResourceType_RESOURCE_TYPE_FILE: - return "file" - case provider.ResourceType_RESOURCE_TYPE_CONTAINER: - return "folder" - case provider.ResourceType_RESOURCE_TYPE_REFERENCE: - return "reference" - case provider.ResourceType_RESOURCE_TYPE_SYMLINK: - return "symlink" - default: - return "" - } -} - -func sharePermToInt(p *provider.ResourcePermissions) int { - return int(conversions.RoleFromResourcePermissions(p, false).OCSPermissions()) -} - -func intTosharePerm(p int) (*provider.ResourcePermissions, error) { - perms, err := conversions.NewPermissions(p) - if err != nil { - return nil, err - } - - return conversions.RoleFromOCSPermissions(perms, nil).CS3ResourcePermissions(), nil -} - -func intToShareState(g int) collaboration.ShareState { - switch g { - case 0: - return collaboration.ShareState_SHARE_STATE_ACCEPTED - case 1: - return collaboration.ShareState_SHARE_STATE_PENDING - case 2: - return collaboration.ShareState_SHARE_STATE_REJECTED - default: - return collaboration.ShareState_SHARE_STATE_INVALID - } -} - -func formatUserID(u *userpb.UserId) string { - return u.OpaqueId -} - -func formatGroupID(u *grouppb.GroupId) string { - return u.OpaqueId -} - -func extractGroupID(u string) *grouppb.GroupId { - return &grouppb.GroupId{OpaqueId: u} -} - -func (m *mgr) convertToCS3Share(ctx context.Context, s DBShare, storageMountID string) (*collaboration.Share, error) { - ts := &typespb.Timestamp{ - Seconds: uint64(s.STime), - } - permissions, err := intTosharePerm(s.Permissions) - if err != nil { - return nil, err - } - grantee, err := m.extractGrantee(ctx, s.ShareType, s.ShareWith) - if err != nil { - return nil, err - } - owner, err := m.userConverter.UserNameToUserID(ctx, s.UIDOwner) - if err != nil { - return nil, err - } - var creator *userpb.UserId - if s.UIDOwner == s.UIDInitiator { - creator = owner - } else { - creator, err = m.userConverter.UserNameToUserID(ctx, s.UIDOwner) - if err != nil { - return nil, err - } - } - return &collaboration.Share{ - Id: &collaboration.ShareId{ - OpaqueId: s.ID, - }, - ResourceId: &provider.ResourceId{ - SpaceId: s.ItemStorage, - OpaqueId: s.FileSource, - }, - Permissions: &collaboration.SharePermissions{Permissions: permissions}, - Grantee: grantee, - Owner: owner, - Creator: creator, - Ctime: ts, - Mtime: ts, - }, nil -} - -func (m *mgr) convertToCS3ReceivedShare(ctx context.Context, s DBShare, storageMountID string) (*collaboration.ReceivedShare, error) { - share, err := m.convertToCS3Share(ctx, s, storageMountID) - if err != nil { - return nil, err - } - var state collaboration.ShareState - if s.RejectedBy != "" { - state = collaboration.ShareState_SHARE_STATE_REJECTED - } else { - state = intToShareState(s.State) - } - return &collaboration.ReceivedShare{ - Share: share, - State: state, - MountPoint: &provider.Reference{Path: strings.TrimLeft(s.FileTarget, "/")}, - }, nil -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/owncloudsql.go b/vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/owncloudsql.go deleted file mode 100644 index e0dbc019bdb..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/owncloudsql.go +++ /dev/null @@ -1,674 +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 owncloudsql - -import ( - "context" - "database/sql" - "fmt" - "path" - "strconv" - "strings" - "time" - - 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" - ctxpkg "github.com/owncloud/reva/v2/pkg/ctx" - "github.com/owncloud/reva/v2/pkg/errtypes" - "github.com/owncloud/reva/v2/pkg/share" - "github.com/owncloud/reva/v2/pkg/share/manager/registry" - "github.com/owncloud/reva/v2/pkg/utils" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" - "google.golang.org/genproto/protobuf/field_mask" - - // Provides mysql drivers - _ "github.com/go-sql-driver/mysql" -) - -const ( - shareTypeUser = 0 - shareTypeGroup = 1 -) - -func init() { - registry.Register("owncloudsql", NewMysql) -} - -type config struct { - GatewayAddr string `mapstructure:"gateway_addr"` - StorageMountID string `mapstructure:"storage_mount_id"` - DbUsername string `mapstructure:"db_username"` - DbPassword string `mapstructure:"db_password"` - DbHost string `mapstructure:"db_host"` - DbPort int `mapstructure:"db_port"` - DbName string `mapstructure:"db_name"` -} - -type mgr struct { - driver string - db *sql.DB - storageMountID string - userConverter UserConverter -} - -// NewMysql returns a new share manager connection to a mysql database -func NewMysql(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 - } - - db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DbUsername, c.DbPassword, c.DbHost, c.DbPort, c.DbName)) - if err != nil { - return nil, err - } - - userConverter := NewGatewayUserConverter(c.GatewayAddr) - - return New("mysql", db, c.StorageMountID, userConverter) -} - -// New returns a new Cache instance connecting to the given sql.DB -func New(driver string, db *sql.DB, storageMountID string, userConverter UserConverter) (share.Manager, error) { - return &mgr{ - driver: driver, - db: db, - storageMountID: storageMountID, - userConverter: userConverter, - }, nil -} - -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 (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collaboration.ShareGrant) (*collaboration.Share, error) { - user := ctxpkg.ContextMustGetUser(ctx) - - // 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 && - (utils.UserEqual(g.Grantee.GetUserId(), user.Id) || utils.UserEqual(g.Grantee.GetUserId(), md.Owner)) { - return nil, errtypes.BadRequest("owncloudsql: owner/creator and grantee are the same") - } - - // check if share already exists. - key := &collaboration.ShareKey{ - Owner: md.Owner, - ResourceId: md.Id, - Grantee: g.Grantee, - } - _, err := m.getByKey(ctx, key) - - // share already exists - if err == nil { - return nil, errtypes.AlreadyExists(key.String()) - } - - now := time.Now().Unix() - ts := &typespb.Timestamp{ - Seconds: uint64(now), - } - - owner, err := m.userConverter.UserIDToUserName(ctx, md.Owner) - if err != nil { - return nil, err - } - shareType, shareWith, err := m.formatGrantee(ctx, g.Grantee) - if err != nil { - return nil, err - } - itemType := resourceTypeToItem(md.Type) - targetPath := path.Join("/", path.Base(md.Path)) - permissions := sharePermToInt(g.Permissions.Permissions) - itemSource := md.Id.OpaqueId - fileSource, err := strconv.ParseUint(itemSource, 10, 64) - if err != nil { - // it can be the case that the item source may be a character string - // we leave fileSource blank in that case - fileSource = 0 - } - - stmtString := "INSERT INTO oc_share (share_type,uid_owner,uid_initiator,item_type,item_source,file_source,permissions,stime,share_with,file_target) VALUES (?,?,?,?,?,?,?,?,?,?)" - stmtValues := []interface{}{shareType, owner, user.Username, itemType, itemSource, fileSource, permissions, now, shareWith, targetPath} - - stmt, err := m.db.Prepare(stmtString) - if err != nil { - return nil, err - } - result, err := stmt.ExecContext(ctx, stmtValues...) - if err != nil { - return nil, err - } - lastID, err := result.LastInsertId() - if err != nil { - return nil, err - } - - return &collaboration.Share{ - Id: &collaboration.ShareId{ - OpaqueId: strconv.FormatInt(lastID, 10), - }, - ResourceId: md.Id, - Permissions: g.Permissions, - Grantee: g.Grantee, - Owner: md.Owner, - Creator: user.Id, - Ctime: ts, - Mtime: ts, - }, nil -} - -func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.Share, error) { - var s *collaboration.Share - var 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 - } - - return s, nil -} - -func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) error { - uid := ctxpkg.ContextMustGetUser(ctx).Username - var query string - params := []interface{}{} - switch { - case ref.GetId() != nil: - query = "DELETE FROM oc_share where id=? AND (uid_owner=? or uid_initiator=?)" - params = append(params, ref.GetId().OpaqueId, uid, uid) - case ref.GetKey() != nil: - key := ref.GetKey() - shareType, shareWith, err := m.formatGrantee(ctx, key.Grantee) - if err != nil { - return err - } - owner := formatUserID(key.Owner) - query = "DELETE FROM oc_share WHERE uid_owner=? AND file_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" - params = append(params, owner, key.ResourceId.OpaqueId, shareType, shareWith, uid, uid) - default: - return errtypes.NotFound(ref.String()) - } - - stmt, err := m.db.Prepare(query) - if err != nil { - return err - } - res, err := stmt.ExecContext(ctx, params...) - if err != nil { - return err - } - - rowCnt, err := res.RowsAffected() - if err != nil { - return err - } - if rowCnt == 0 { - return errtypes.NotFound(ref.String()) - } - return nil -} - -func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions, updated *collaboration.Share, fieldMask *field_mask.FieldMask) (*collaboration.Share, error) { - permissions := sharePermToInt(p.Permissions) - uid := ctxpkg.ContextMustGetUser(ctx).Username - - var query string - params := []interface{}{} - switch { - case ref.GetId() != nil: - query = "update oc_share set permissions=?,stime=? where id=? AND (uid_owner=? or uid_initiator=?)" - params = append(params, permissions, time.Now().Unix(), ref.GetId().OpaqueId, uid, uid) - case ref.GetKey() != nil: - key := ref.GetKey() - shareType, shareWith, err := m.formatGrantee(ctx, key.Grantee) - if err != nil { - return nil, err - } - owner := formatUserID(key.Owner) - query = "update oc_share set permissions=?,stime=? where (uid_owner=? or uid_initiator=?) AND file_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" - params = append(params, permissions, time.Now().Unix(), owner, owner, key.ResourceId.OpaqueId, shareType, shareWith, uid, uid) - default: - return nil, errtypes.NotFound(ref.String()) - } - - stmt, err := m.db.Prepare(query) - if err != nil { - return nil, err - } - if _, err = stmt.ExecContext(ctx, params...); err != nil { - return nil, err - } - - return m.GetShare(ctx, ref) -} - -func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { - uid := ctxpkg.ContextMustGetUser(ctx).Username - query := ` - SELECT - coalesce(s.uid_owner, '') as uid_owner, coalesce(s.uid_initiator, '') as uid_initiator, - coalesce(s.share_with, '') as share_with, coalesce(s.file_source, '') as file_source, - s.file_target, s.id, s.stime, s.permissions, s.share_type, fc.storage as storage - FROM oc_share s - LEFT JOIN oc_filecache fc ON fc.fileid = file_source - WHERE (uid_owner=? or uid_initiator=?) - ` - params := []interface{}{uid, uid} - - var ( - filterQuery string - filterParams []interface{} - err error - ) - if len(filters) == 0 { - filterQuery += "(share_type=? OR share_type=?)" - params = append(params, shareTypeUser) - params = append(params, shareTypeGroup) - } else { - filterQuery, filterParams, err = translateFilters(filters) - if err != nil { - return nil, err - } - params = append(params, filterParams...) - } - - if filterQuery != "" { - query = fmt.Sprintf("%s AND (%s)", query, filterQuery) - } - - rows, err := m.db.QueryContext(ctx, query, params...) - if err != nil { - return nil, err - } - defer rows.Close() - - var s DBShare - shares := []*collaboration.Share{} - for rows.Next() { - if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.ItemStorage); err != nil { - continue - } - share, err := m.convertToCS3Share(ctx, s, m.storageMountID) - if err != nil { - return nil, err - } - shares = append(shares, share) - } - if err = rows.Err(); err != nil { - return nil, err - } - - return shares, nil -} - -// we list the shares that are targeted to the user in context or to the user groups. -func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter, forUser *userpb.UserId) ([]*collaboration.ReceivedShare, error) { - user := ctxpkg.ContextMustGetUser(ctx) - if user.GetId().GetType() == userpb.UserType_USER_TYPE_SERVICE { - u, err := m.userConverter.GetUser(forUser) - if err != nil { - return nil, errtypes.BadRequest("user not found") - } - user = u - } - uid := user.Username - - params := []interface{}{uid, uid, uid} - for _, v := range user.Groups { - params = append(params, v) - } - - homeConcat := "" - if m.driver == "mysql" { // mysql concat - homeConcat = "storages.id = CONCAT('home::', s.uid_owner)" - } else { // sqlite3 concat - homeConcat = "storages.id = 'home::' || s.uid_owner" - } - userSelect := "" - if len(user.Groups) > 0 { - userSelect = "AND ((share_type != 1 AND share_with=?) OR (share_type = 1 AND share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + ")))" - } else { - userSelect = "AND (share_type != 1 AND share_with=?)" - } - query := ` - WITH results AS - ( - SELECT s.*, storages.numeric_id FROM oc_share s - LEFT JOIN oc_storages storages ON ` + homeConcat + ` - WHERE (uid_owner != ? AND uid_initiator != ?) ` + userSelect + ` - ) - SELECT COALESCE(r.uid_owner, '') AS uid_owner, COALESCE(r.uid_initiator, '') AS uid_initiator, COALESCE(r.share_with, '') - AS share_with, COALESCE(r.file_source, '') AS file_source, COALESCE(r2.file_target, r.file_target), r.id, r.stime, r.permissions, r.share_type, COALESCE(r2.accepted, r.accepted), - r.numeric_id, COALESCE(r.parent, -1) AS parent FROM results r LEFT JOIN results r2 ON r.id = r2.parent WHERE r.parent IS NULL` - - filterQuery, filterParams, err := translateFilters(filters) - if err != nil { - return nil, err - } - params = append(params, filterParams...) - - if filterQuery != "" { - query = fmt.Sprintf("%s AND (%s)", query, filterQuery) - } - query += ";" - rows, err := m.db.QueryContext(ctx, query, params...) - if err != nil { - return nil, err - } - defer rows.Close() - - var s DBShare - shares := []*collaboration.ReceivedShare{} - for rows.Next() { - if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State, &s.ItemStorage, &s.Parent); err != nil { - continue - } - share, err := m.convertToCS3ReceivedShare(ctx, s, m.storageMountID) - if err != nil { - return nil, err - } - shares = append(shares, share) - } - if err = rows.Err(); err != nil { - return nil, err - } - - return shares, nil -} - -func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { - var s *collaboration.ReceivedShare - var err error - switch { - case ref.GetId() != nil: - s, err = m.getReceivedByID(ctx, ref.GetId()) - case ref.GetKey() != nil: - s, err = m.getReceivedByKey(ctx, ref.GetKey()) - default: - err = errtypes.NotFound(ref.String()) - } - - if err != nil { - return nil, err - } - - return s, nil - -} - -func (m *mgr) UpdateReceivedShare(ctx context.Context, receivedShare *collaboration.ReceivedShare, fieldMask *field_mask.FieldMask, _ *userpb.UserId) (*collaboration.ReceivedShare, error) { - // TODO: How to inject the uid when a UserId is set? override it in the ctx? Add parameter to GetReceivedShare? - rs, err := m.GetReceivedShare(ctx, &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{Id: receivedShare.Share.Id}}) - if err != nil { - return nil, err - } - - fields := []string{} - params := []interface{}{} - for i := range fieldMask.Paths { - switch fieldMask.Paths[i] { - case "state": - rs.State = receivedShare.State - fields = append(fields, "accepted=?") - switch rs.State { - case collaboration.ShareState_SHARE_STATE_REJECTED: - params = append(params, 2) - case collaboration.ShareState_SHARE_STATE_ACCEPTED: - params = append(params, 0) - } - case "mount_point": - fields = append(fields, "file_target=?") - rs.MountPoint = receivedShare.MountPoint - params = append(params, rs.MountPoint.Path) - case "hidden": - continue - default: - return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") - } - } - - if len(fields) == 0 { - return nil, fmt.Errorf("no valid field provided in the fieldmask") - } - - updateReceivedShare := func(column string) error { - query := "update oc_share set " - query += strings.Join(fields, ",") - query += fmt.Sprintf(" where %s=?", column) - params := append(params, rs.Share.Id.OpaqueId) - - stmt, err := m.db.Prepare(query) - if err != nil { - return err - } - res, err := stmt.ExecContext(ctx, params...) - if err != nil { - return err - } - affected, err := res.RowsAffected() - if err != nil { - return err - } - if affected < 1 { - return fmt.Errorf("No rows updated") - } - return nil - } - err = updateReceivedShare("parent") // Try to update the child state in case of group shares first - if err != nil { - err = updateReceivedShare("id") - } - if err != nil { - return nil, err - } - - return rs, nil -} - -func (m *mgr) getByID(ctx context.Context, id *collaboration.ShareId) (*collaboration.Share, error) { - uid := ctxpkg.ContextMustGetUser(ctx).Username - s := DBShare{ID: id.OpaqueId} - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, file_target, stime, permissions, share_type FROM oc_share WHERE id=? AND (uid_owner=? or uid_initiator=?)" - if err := m.db.QueryRowContext(ctx, query, id.OpaqueId, uid, uid).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.STime, &s.Permissions, &s.ShareType); err != nil { - if err == sql.ErrNoRows { - return nil, errtypes.NotFound(id.OpaqueId) - } - return nil, err - } - return m.convertToCS3Share(ctx, s, m.storageMountID) -} - -func (m *mgr) getByKey(ctx context.Context, key *collaboration.ShareKey) (*collaboration.Share, error) { - owner, err := m.userConverter.UserIDToUserName(ctx, key.Owner) - if err != nil { - return nil, err - } - uid := ctxpkg.ContextMustGetUser(ctx).Username - - s := DBShare{} - shareType, shareWith, err := m.formatGrantee(ctx, key.Grantee) - if err != nil { - return nil, err - } - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, file_target, id, stime, permissions, share_type FROM oc_share WHERE uid_owner=? AND file_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" - if err = m.db.QueryRowContext(ctx, query, owner, key.ResourceId.StorageId, shareType, shareWith, uid, uid).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { - if err == sql.ErrNoRows { - return nil, errtypes.NotFound(key.String()) - } - return nil, err - } - return m.convertToCS3Share(ctx, s, m.storageMountID) -} - -func (m *mgr) getReceivedByID(ctx context.Context, id *collaboration.ShareId) (*collaboration.ReceivedShare, error) { - user := ctxpkg.ContextMustGetUser(ctx) - uid := user.Username - - params := []interface{}{id.OpaqueId, id.OpaqueId, uid} - for _, v := range user.Groups { - params = append(params, v) - } - - homeConcat := "" - if m.driver == "mysql" { // mysql concat - homeConcat = "storages.id = CONCAT('home::', s.uid_owner)" - } else { // sqlite3 concat - homeConcat = "storages.id = 'home::' || s.uid_owner" - } - userSelect := "" - if len(user.Groups) > 0 { - userSelect = "AND ((share_type != 1 AND share_with=?) OR (share_type = 1 AND share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + ")))" - } else { - userSelect = "AND (share_type != 1 AND share_with=?)" - } - - query := ` - WITH results AS - ( - SELECT s.*, storages.numeric_id - FROM oc_share s - LEFT JOIN oc_storages storages ON ` + homeConcat + ` - WHERE s.id=? OR s.parent=? ` + userSelect + ` - ) - SELECT COALESCE(r.uid_owner, '') AS uid_owner, COALESCE(r.uid_initiator, '') AS uid_initiator, COALESCE(r.share_with, '') - AS share_with, COALESCE(r.file_source, '') AS file_source, COALESCE(r2.file_target, r.file_target), r.id, r.stime, r.permissions, r.share_type, COALESCE(r2.accepted, r.accepted), - r.numeric_id, COALESCE(r.parent, -1) AS parent - FROM results r - LEFT JOIN results r2 ON r.id = r2.parent - WHERE r.parent IS NULL; - ` - - s := DBShare{} - if err := m.db.QueryRowContext(ctx, query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State, &s.ItemStorage, &s.Parent); err != nil { - if err == sql.ErrNoRows { - return nil, errtypes.NotFound(id.OpaqueId) - } - return nil, err - } - return m.convertToCS3ReceivedShare(ctx, s, m.storageMountID) -} - -func (m *mgr) getReceivedByKey(ctx context.Context, key *collaboration.ShareKey) (*collaboration.ReceivedShare, error) { - user := ctxpkg.ContextMustGetUser(ctx) - uid := user.Username - - shareType, shareWith, err := m.formatGrantee(ctx, key.Grantee) - if err != nil { - return nil, err - } - params := []interface{}{uid, formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith, shareWith} - for _, v := range user.Groups { - params = append(params, v) - } - - s := DBShare{} - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, file_target, ts.id, stime, permissions, share_type, accepted FROM oc_share ts WHERE uid_owner=? AND file_source=? AND share_type=? AND share_with=? " - if len(user.Groups) > 0 { - query += "AND (share_with=? OR share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + "))" - } else { - query += "AND (share_with=?)" - } - - if err := m.db.QueryRowContext(ctx, query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { - if err == sql.ErrNoRows { - return nil, errtypes.NotFound(key.String()) - } - return nil, err - } - return m.convertToCS3ReceivedShare(ctx, s, m.storageMountID) -} - -func granteeTypeToShareType(granteeType provider.GranteeType) int { - switch granteeType { - case provider.GranteeType_GRANTEE_TYPE_USER: - return shareTypeUser - case provider.GranteeType_GRANTEE_TYPE_GROUP: - return shareTypeGroup - } - return -1 -} - -// translateFilters translates the filters to sql queries -func translateFilters(filters []*collaboration.Filter) (string, []interface{}, error) { - var ( - filterQuery string - params []interface{} - ) - - groupedFilters := share.GroupFiltersByType(filters) - // If multiple filters of the same type are passed to this function, they need to be combined with the `OR` operator. - // That is why the filters got grouped by type. - // For every given filter type, iterate over the filters and if there are more than one combine them. - // Combine the different filter types using `AND` - var filterCounter = 0 - for filterType, filters := range groupedFilters { - switch filterType { - case collaboration.Filter_TYPE_RESOURCE_ID: - filterQuery += "(" - for i, f := range filters { - filterQuery += "file_source=?" - params = append(params, f.GetResourceId().OpaqueId) - - if i != len(filters)-1 { - filterQuery += " OR " - } - } - filterQuery += ")" - case collaboration.Filter_TYPE_GRANTEE_TYPE: - filterQuery += "(" - for i, f := range filters { - filterQuery += "r.share_type=?" - params = append(params, granteeTypeToShareType(f.GetGranteeType())) - - if i != len(filters)-1 { - filterQuery += " OR " - } - } - filterQuery += ")" - case collaboration.Filter_TYPE_EXCLUDE_DENIALS: - // TODO this may change once the mapping of permission to share types is completed (cf. pkg/cbox/utils/conversions.go) - filterQuery += "r.permissions > 0" - default: - return "", nil, fmt.Errorf("filter type is not supported") - } - if filterCounter != len(groupedFilters)-1 { - filterQuery += " AND " - } - filterCounter++ - } - return filterQuery, params, nil -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/test.db b/vendor/github.com/owncloud/reva/v2/pkg/share/manager/owncloudsql/test.db deleted file mode 100644 index fba76fdcc4fc7816bf2b281f9c2074bf1955842c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 655360 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 diff --git a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/loader/loader.go b/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/loader/loader.go index ae836ff2da6..e73168385e0 100644 --- a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/loader/loader.go +++ b/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/loader/loader.go @@ -32,7 +32,6 @@ import ( _ "github.com/owncloud/reva/v2/pkg/storage/fs/localhome" _ "github.com/owncloud/reva/v2/pkg/storage/fs/nextcloud" _ "github.com/owncloud/reva/v2/pkg/storage/fs/ocis" - _ "github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql" _ "github.com/owncloud/reva/v2/pkg/storage/fs/s3" _ "github.com/owncloud/reva/v2/pkg/storage/fs/s3ng" // Add your own here diff --git a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/filecache/filecache.go b/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/filecache/filecache.go deleted file mode 100644 index 42f441d8a37..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/filecache/filecache.go +++ /dev/null @@ -1,761 +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 filecache - -import ( - "context" - "crypto/md5" - "database/sql" - "encoding/hex" - "fmt" - "path/filepath" - "regexp" - "strconv" - "strings" - "time" - - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/owncloud/reva/v2/pkg/conversions" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - - // Provides mysql drivers - _ "github.com/go-sql-driver/mysql" -) - -// Cache represents a oc10-style file cache -type Cache struct { - driver string - db *sql.DB -} - -// Storage represents a storage entry in the database -type Storage struct { - ID string - NumericID int -} - -// File represents an entry of the file cache -type File struct { - ID int - Storage int - Parent int - MimePart int - MimeType int - MimeTypeString string - Size int - MTime int - StorageMTime int - UnencryptedSize int - Permissions int - Encrypted bool - Path string - Name string - Etag string - Checksum string -} - -// TrashItem represents a trash item of the file cache -type TrashItem struct { - ID int - Name string - User string - Path string - Timestamp int -} - -// Scannable describes the interface providing a Scan method -type Scannable interface { - Scan(...interface{}) error -} - -// NewMysql returns a new Cache instance connecting to a MySQL database -func NewMysql(dsn string) (*Cache, error) { - sqldb, err := sql.Open("mysql", dsn) - if err != nil { - return nil, errors.Wrap(err, "error connecting to the database") - } - - // FIXME make configurable - sqldb.SetConnMaxLifetime(time.Minute * 3) - sqldb.SetConnMaxIdleTime(time.Second * 30) - sqldb.SetMaxOpenConns(100) - sqldb.SetMaxIdleConns(10) - - err = sqldb.Ping() - if err != nil { - return nil, errors.Wrap(err, "error connecting to the database") - } - - return New("mysql", sqldb) -} - -// New returns a new Cache instance connecting to the given sql.DB -func New(driver string, sqldb *sql.DB) (*Cache, error) { - return &Cache{ - driver: driver, - db: sqldb, - }, nil -} - -// ListStorages returns the list of numeric ids of all storages -// Optionally only home storages are considered -func (c *Cache) ListStorages(ctx context.Context, onlyHome bool) ([]*Storage, error) { - query := "" - if onlyHome { - mountPointConcat := "" - if c.driver == "mysql" { - mountPointConcat = "m.mount_point = CONCAT('/', m.user_id, '/')" - } else { // sqlite3 - mountPointConcat = "m.mount_point = '/' || m.user_id || '/'" - } - - query = "SELECT s.id, s.numeric_id FROM oc_storages s JOIN oc_mounts m ON s.numeric_id = m.storage_id WHERE " + mountPointConcat - } else { - query = "SELECT id, numeric_id FROM oc_storages" - } - rows, err := c.db.QueryContext(ctx, query) - if err != nil { - return nil, err - } - defer rows.Close() - - storages := []*Storage{} - for rows.Next() { - storage := &Storage{} - err := rows.Scan(&storage.ID, &storage.NumericID) - if err != nil { - return nil, err - } - storages = append(storages, storage) - } - return storages, nil -} - -// GetStorage returns the storage with the given numeric id -func (c *Cache) GetStorage(ctx context.Context, numeridID interface{}) (*Storage, error) { - numericID, err := toIntID(numeridID) - if err != nil { - return nil, err - } - row := c.db.QueryRowContext(ctx, "SELECT id, numeric_id FROM oc_storages WHERE numeric_id = ?", numericID) - s := &Storage{} - switch err := row.Scan(&s.ID, &s.NumericID); err { - case nil: - return s, nil - default: - return nil, err - } -} - -// GetNumericStorageID returns the database id for the given storage -func (c *Cache) GetNumericStorageID(ctx context.Context, id string) (int, error) { - row := c.db.QueryRowContext(ctx, "SELECT numeric_id FROM oc_storages WHERE id = ?", id) - var nid int - switch err := row.Scan(&nid); err { - case nil: - return nid, nil - default: - return -1, err - } -} - -// CreateStorage creates a new storage and returns its numeric id -func (c *Cache) CreateStorage(ctx context.Context, id string) (int, error) { - tx, err := c.db.Begin() - if err != nil { - return -1, err - } - defer func() { _ = tx.Rollback() }() - - stmt, err := tx.PrepareContext(ctx, "INSERT INTO oc_storages(id) VALUES(?)") - if err != nil { - return -1, err - } - defer stmt.Close() - - res, err := stmt.ExecContext(ctx, id) - if err != nil { - return -1, err - } - insertedID, err := res.LastInsertId() - if err != nil { - return -1, err - } - - data := map[string]interface{}{ - "path": "", - "etag": "", - "mimetype": "httpd/unix-directory", - } - _, err = c.doInsertOrUpdate(ctx, tx, int(insertedID), data, true) - if err != nil { - return -1, err - } - - err = tx.Commit() - if err != nil { - return -1, err - } - - return int(insertedID), err -} - -// GetStorageOwner returns the username of the owner of the given storage -func (c *Cache) GetStorageOwner(ctx context.Context, numericID interface{}) (string, error) { - numericID, err := toIntID(numericID) - if err != nil { - return "", err - } - row := c.db.QueryRowContext(ctx, "SELECT id FROM oc_storages WHERE numeric_id = ?", numericID) - var id string - switch err := row.Scan(&id); err { - case nil: - return strings.TrimPrefix(id, "home::"), nil - default: - return "", err - } -} - -// GetStorageOwnerByFileID returns the username of the owner of the given entry -func (c *Cache) GetStorageOwnerByFileID(ctx context.Context, numericID interface{}) (string, error) { - numericID, err := toIntID(numericID) - if err != nil { - return "", err - } - row := c.db.QueryRowContext(ctx, "SELECT id FROM oc_storages storages, oc_filecache cache WHERE storages.numeric_id = cache.storage AND cache.fileid = ?", numericID) - var id string - switch err := row.Scan(&id); err { - case nil: - return strings.TrimPrefix(id, "home::"), nil - default: - return "", err - } -} - -func (c *Cache) rowToFile(row Scannable) (*File, error) { - var fileid, storage, parent, mimetype, mimepart, size, mtime, storageMtime, encrypted, unencryptedSize int - var permissions sql.NullInt32 - var path, name, etag, checksum, mimetypestring sql.NullString - err := row.Scan(&fileid, &storage, &path, &parent, &permissions, &mimetype, &mimepart, &mimetypestring, &size, &mtime, &storageMtime, &encrypted, &unencryptedSize, &name, &etag, &checksum) - if err != nil { - return nil, err - } - - return &File{ - ID: fileid, - Storage: storage, - Path: path.String, - Parent: parent, - Permissions: int(permissions.Int32), - MimeType: mimetype, - MimeTypeString: mimetypestring.String, - MimePart: mimepart, - Size: size, - MTime: mtime, - StorageMTime: storageMtime, - Encrypted: encrypted == 1, - UnencryptedSize: unencryptedSize, - Name: name.String, - Etag: etag.String, - Checksum: checksum.String, - }, nil -} - -// Get returns the cache entry for the specified storage/path -func (c *Cache) Get(ctx context.Context, s interface{}, p string) (*File, error) { - storageID, err := toIntID(s) - if err != nil { - return nil, err - } - - phashBytes := md5.Sum([]byte(p)) - phash := hex.EncodeToString(phashBytes[:]) - - row := c.db.QueryRowContext(ctx, ` - SELECT - fc.fileid, fc.storage, fc.path, fc.parent, fc.permissions, fc.mimetype, fc.mimepart, - mt.mimetype, fc.size, fc.mtime, fc.storage_mtime, fc.encrypted, fc.unencrypted_size, - fc.name, fc.etag, fc.checksum - FROM oc_filecache fc - LEFT JOIN oc_mimetypes mt ON fc.mimetype = mt.id - WHERE path_hash = ? AND storage = ?`, phash, storageID) - return c.rowToFile(row) -} - -// Path returns the path for the specified entry -func (c *Cache) Path(ctx context.Context, id interface{}) (string, error) { - id, err := toIntID(id) - if err != nil { - return "", err - } - - row := c.db.QueryRowContext(ctx, "SELECT path FROM oc_filecache WHERE fileid = ?", id) - var path string - err = row.Scan(&path) - if err != nil { - return "", err - } - return path, nil -} - -// List returns the list of entries below the given path -func (c *Cache) List(ctx context.Context, storage interface{}, p string) ([]*File, error) { - storageID, err := toIntID(storage) - if err != nil { - return nil, err - } - - var rows *sql.Rows - phash := fmt.Sprintf("%x", md5.Sum([]byte(strings.Trim(p, "/")))) - rows, err = c.db.QueryContext(ctx, ` - SELECT - fc.fileid, fc.storage, fc.path, fc.parent, fc.permissions, fc.mimetype, fc.mimepart, - mt.mimetype, fc.size, fc.mtime, fc.storage_mtime, fc.encrypted, fc.unencrypted_size, - fc.name, fc.etag, fc.checksum - FROM oc_filecache fc - LEFT JOIN oc_mimetypes mt ON fc.mimetype = mt.id - WHERE storage = ? AND parent = (SELECT fileid FROM oc_filecache WHERE storage = ? AND path_hash=?) AND name IS NOT NULL - `, storageID, storageID, phash) - if err != nil { - return nil, err - } - defer rows.Close() - entries := []*File{} - for rows.Next() { - entry, err := c.rowToFile(rows) - if err != nil { - return nil, err - } - entries = append(entries, entry) - } - - return entries, nil -} - -// Permissions returns the permissions for the specified storage/path -func (c *Cache) Permissions(ctx context.Context, storage interface{}, p string) (*provider.ResourcePermissions, error) { - entry, err := c.Get(ctx, storage, p) - if err != nil { - return nil, err - } - - perms, err := conversions.NewPermissions(entry.Permissions) - if err != nil { - return nil, err - } - - return conversions.RoleFromOCSPermissions(perms, nil).CS3ResourcePermissions(), nil -} - -// InsertOrUpdate creates or updates a cache entry -func (c *Cache) InsertOrUpdate(ctx context.Context, storage interface{}, data map[string]interface{}, allowEmptyParent bool) (int, error) { - tx, err := c.db.Begin() - if err != nil { - return -1, err - } - defer func() { _ = tx.Rollback() }() - - id, err := c.doInsertOrUpdate(ctx, tx, storage, data, allowEmptyParent) - if err != nil { - return -1, err - } - - err = tx.Commit() - if err != nil { - return -1, err - } - - return id, err -} - -func (c *Cache) doInsertOrUpdate(ctx context.Context, tx *sql.Tx, storage interface{}, data map[string]interface{}, allowEmptyParent bool) (int, error) { - storageID, err := toIntID(storage) - if err != nil { - return -1, err - } - - columns := []string{"storage"} - placeholders := []string{"?"} - values := []interface{}{storage} - - for _, key := range []string{"path", "mimetype", "etag"} { - if _, exists := data[key]; !exists { - return -1, fmt.Errorf("missing required data") - } - } - - path := data["path"].(string) - data["name"] = filepath.Base(path) - if data["name"] == "." { - data["name"] = "" - } - - parentPath := strings.TrimRight(filepath.Dir(path), "/") - if parentPath == "." { - parentPath = "" - } - if path == "" { - data["parent"] = -1 - } else { - parent, err := c.Get(ctx, storageID, parentPath) - if err == nil { - data["parent"] = parent.ID - } else { - if allowEmptyParent { - data["parent"] = -1 - } else { - return -1, fmt.Errorf("could not find parent %s, %s, %v, %w", parentPath, path, parent, err) - } - } - } - - if _, exists := data["checksum"]; !exists { - data["checksum"] = "" - } - - for k, v := range data { - switch k { - case "path": - phashBytes := md5.Sum([]byte(v.(string))) - phash := hex.EncodeToString(phashBytes[:]) - columns = append(columns, "path_hash") - values = append(values, phash) - placeholders = append(placeholders, "?") - case "storage_mtime": - if _, exists := data["mtime"]; !exists { - columns = append(columns, "mtime") - values = append(values, v) - placeholders = append(placeholders, "?") - } - case "mimetype": - parts := strings.Split(v.(string), "/") - columns = append(columns, "mimetype") - values = append(values, v) - placeholders = append(placeholders, "(SELECT id FROM oc_mimetypes WHERE mimetype=?)") - columns = append(columns, "mimepart") - values = append(values, parts[0]) - placeholders = append(placeholders, "(SELECT id FROM oc_mimetypes WHERE mimetype=?)") - continue - } - - columns = append(columns, k) - values = append(values, v) - placeholders = append(placeholders, "?") - } - - err = c.insertMimetype(ctx, tx, data["mimetype"].(string)) - if err != nil { - return -1, err - } - - query := "INSERT INTO oc_filecache( " + strings.Join(columns, ", ") + ") VALUES(" + strings.Join(placeholders, ",") + ")" - - updates := []string{} - for i, column := range columns { - if column != "path" && column != "path_hash" && column != "storage" { - updates = append(updates, column+"="+placeholders[i]) - values = append(values, values[i]) - } - } - if c.driver == "mysql" { // mysql upsert - query += " ON DUPLICATE KEY UPDATE " - } else { // sqlite3 upsert - query += " ON CONFLICT(storage,path_hash) DO UPDATE SET " - } - query += strings.Join(updates, ",") - - stmt, err := tx.PrepareContext(ctx, query) - if err != nil { - return -1, err - } - - res, err := stmt.ExecContext(ctx, values...) - if err != nil { - log.Err(err).Msg("could not store filecache item") - return -1, err - } - id, err := res.LastInsertId() - if err != nil { - return -1, err - } - return int(id), nil -} - -// Copy creates a copy of the specified entry at the target path -func (c *Cache) Copy(ctx context.Context, storage interface{}, sourcePath, targetPath string) (int, error) { - storageID, err := toIntID(storage) - if err != nil { - return -1, err - } - source, err := c.Get(ctx, storageID, sourcePath) - if err != nil { - return -1, errors.Wrap(err, "could not find source") - } - - row := c.db.QueryRowContext(ctx, "SELECT mimetype FROM oc_mimetypes WHERE id=?", source.MimeType) - var mimetype string - err = row.Scan(&mimetype) - if err != nil { - return -1, errors.Wrap(err, "could not find source mimetype") - } - - data := map[string]interface{}{ - "path": targetPath, - "checksum": source.Checksum, - "mimetype": mimetype, - "permissions": source.Permissions, - "etag": source.Etag, - "size": source.Size, - "mtime": source.MTime, - "storage_mtime": source.StorageMTime, - "encrypted": source.Encrypted, - "unencrypted_size": source.UnencryptedSize, - } - return c.InsertOrUpdate(ctx, storage, data, false) -} - -// Move moves the specified entry to the target path -func (c *Cache) Move(ctx context.Context, storage interface{}, sourcePath, targetPath string) error { - storageID, err := toIntID(storage) - if err != nil { - return err - } - source, err := c.Get(ctx, storageID, sourcePath) - if err != nil { - return errors.Wrap(err, "could not find source") - } - newParentPath := strings.TrimRight(filepath.Dir(targetPath), "/") - newParent, err := c.Get(ctx, storageID, newParentPath) - if err != nil { - return errors.Wrap(err, "could not find new parent") - } - - tx, err := c.db.Begin() - if err != nil { - return err - } - defer func() { _ = tx.Rollback() }() - stmt, err := tx.Prepare("UPDATE oc_filecache SET parent=?, path=?, name=?, path_hash=? WHERE storage = ? AND fileid=?") - if err != nil { - return err - } - defer stmt.Close() - phashBytes := md5.Sum([]byte(targetPath)) - _, err = stmt.ExecContext(ctx, newParent.ID, targetPath, filepath.Base(targetPath), hex.EncodeToString(phashBytes[:]), storageID, source.ID) - if err != nil { - return err - } - - childRows, err := tx.QueryContext(ctx, "SELECT fileid, path FROM oc_filecache WHERE parent = ?", source.ID) - if err != nil { - return err - } - defer childRows.Close() - children := map[int]string{} - for childRows.Next() { - var ( - id int - path string - ) - err = childRows.Scan(&id, &path) - if err != nil { - return err - } - - children[id] = path - } - for id, path := range children { - path = strings.ReplaceAll(path, sourcePath, targetPath) - phashBytes = md5.Sum([]byte(path)) - _, err = stmt.ExecContext(ctx, source.ID, path, filepath.Base(path), hex.EncodeToString(phashBytes[:]), storageID, id) - if err != nil { - return err - } - } - - return tx.Commit() -} - -// Purge removes the specified storage/path from the cache without putting it into the trash -func (c *Cache) Purge(ctx context.Context, storage interface{}, path string) error { - storageID, err := toIntID(storage) - if err != nil { - return err - } - phashBytes := md5.Sum([]byte(path)) - phash := hex.EncodeToString(phashBytes[:]) - _, err = c.db.ExecContext(ctx, "DELETE FROM oc_filecache WHERE storage = ? and path_hash = ?", storageID, phash) - return err -} - -// Delete removes the specified storage/path from the cache -func (c *Cache) Delete(ctx context.Context, storage interface{}, user, path, trashPath string) error { - err := c.Move(ctx, storage, path, trashPath) - if err != nil { - return err - } - - re := regexp.MustCompile(`(.*)\.d(\d+)$`) - parts := re.FindStringSubmatch(filepath.Base(trashPath)) - - query := "INSERT INTO oc_files_trash(user,id,timestamp,location) VALUES(?,?,?,?)" - stmt, err := c.db.PrepareContext(ctx, query) - if err != nil { - return err - } - - relativeLocation, err := filepath.Rel("files/", filepath.Dir(path)) - if err != nil { - return err - } - _, err = stmt.ExecContext(ctx, user, filepath.Base(parts[1]), parts[2], relativeLocation) - if err != nil { - log.Err(err).Msg("could not store filecache item") - return err - } - - return nil -} - -// GetRecycleItem returns the specified recycle item -func (c *Cache) GetRecycleItem(ctx context.Context, user, path string, timestamp int) (*TrashItem, error) { - row := c.db.QueryRowContext(ctx, "SELECT auto_id, id, location FROM oc_files_trash WHERE id = ? AND user = ? AND timestamp = ?", path, user, timestamp) - var autoID int - var id, location string - err := row.Scan(&autoID, &id, &location) - if err != nil { - return nil, err - } - - return &TrashItem{ - ID: autoID, - Name: id, - User: user, - Path: location, - Timestamp: timestamp, - }, nil -} - -// EmptyRecycle clears the recycle bin for the given user -func (c *Cache) EmptyRecycle(ctx context.Context, user string) error { - _, err := c.db.ExecContext(ctx, "DELETE FROM oc_files_trash WHERE user = ?", user) - if err != nil { - return err - } - - storage, err := c.GetNumericStorageID(ctx, "home::"+user) - if err != nil { - return err - } - - _, err = c.db.ExecContext(ctx, "DELETE FROM oc_filecache WHERE storage = ? AND PATH LIKE ?", storage, "files_trashbin/%") - return err -} - -// DeleteRecycleItem deletes the specified item from the trash -func (c *Cache) DeleteRecycleItem(ctx context.Context, user, path string, timestamp int) error { - _, err := c.db.ExecContext(ctx, "DELETE FROM oc_files_trash WHERE id = ? AND user = ? AND timestamp = ?", path, user, timestamp) - return err -} - -// PurgeRecycleItem deletes the specified item from the filecache and the trash -func (c *Cache) PurgeRecycleItem(ctx context.Context, user, path string, timestamp int, isVersionFile bool) error { - row := c.db.QueryRowContext(ctx, "SELECT auto_id, location FROM oc_files_trash WHERE id = ? AND user = ? AND timestamp = ?", path, user, timestamp) - var autoID int - var location string - err := row.Scan(&autoID, &location) - if err != nil { - return err - } - - _, err = c.db.ExecContext(ctx, "DELETE FROM oc_files_trash WHERE auto_id=?", autoID) - if err != nil { - return err - } - - storage, err := c.GetNumericStorageID(ctx, "home::"+user) - if err != nil { - return err - } - trashType := "files" - if isVersionFile { - trashType = "versions" - } - item, err := c.Get(ctx, storage, filepath.Join("files_trashbin", trashType, path+".d"+strconv.Itoa(timestamp))) - if err != nil { - return err - } - _, err = c.db.ExecContext(ctx, "DELETE FROM oc_filecache WHERE fileid=? OR parent=?", item.ID, item.ID) - - return err -} - -// SetEtag set a new etag for the specified item -func (c *Cache) SetEtag(ctx context.Context, storage interface{}, path, etag string) error { - storageID, err := toIntID(storage) - if err != nil { - return err - } - source, err := c.Get(ctx, storageID, path) - if err != nil { - return errors.Wrap(err, "could not find source") - } - stmt, err := c.db.PrepareContext(ctx, "UPDATE oc_filecache SET etag=? WHERE storage = ? AND fileid=?") - if err != nil { - return err - } - _, err = stmt.ExecContext(ctx, etag, storageID, source.ID) - return err -} - -func (c *Cache) insertMimetype(ctx context.Context, tx *sql.Tx, mimetype string) error { - insertPart := func(v string) error { - stmt, err := tx.PrepareContext(ctx, "INSERT INTO oc_mimetypes(mimetype) VALUES(?)") - if err != nil { - return err - } - _, err = stmt.ExecContext(ctx, v) - if err != nil { - if strings.Contains(err.Error(), "UNIQUE") || strings.Contains(err.Error(), "Error 1062") { - return nil // Already exists - } - return err - } - return nil - } - parts := strings.Split(mimetype, "/") - err := insertPart(parts[0]) - if err != nil { - return err - } - return insertPart(mimetype) -} - -func toIntID(rid interface{}) (int, error) { - switch t := rid.(type) { - case int: - return t, nil - case string: - return strconv.Atoi(t) - default: - return -1, fmt.Errorf("invalid type") - } -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/filecache/test.db b/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/filecache/test.db deleted file mode 100644 index 913b70f86074a302f4c7b5cc5e42e40c176bcbdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 655360 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 vAb, so we need to insert the files subfolder into the path -// and prefix the data directory -// TODO the path handed to a storage provider should not contain the username -func (fs *owncloudsqlfs) toInternalPath(ctx context.Context, sp string) (ip string) { - if fs.c.EnableHome { - u := ctxpkg.ContextMustGetUser(ctx) - layout := templates.WithUser(u, fs.c.UserLayout) - ip = filepath.Join(fs.c.DataDirectory, layout, "files", sp) - } else { - // trim all / - sp = strings.Trim(sp, "/") - // p = "" or - // p = or - // p = /foo/bar.txt - segments := strings.SplitN(sp, "/", 2) - - if len(segments) == 1 && segments[0] == "" { - ip = fs.c.DataDirectory - return - } - - // parts[0] contains the username or userid. - u, err := fs.getUser(ctx, segments[0]) - if err != nil { - // TODO return invalid internal path? - return - } - layout := templates.WithUser(u, fs.c.UserLayout) - - if len(segments) == 1 { - // parts = "" - ip = filepath.Join(fs.c.DataDirectory, layout, "files") - } else { - // parts = "", "foo/bar.txt" - ip = filepath.Join(fs.c.DataDirectory, layout, "files", segments[1]) - } - - } - return -} - -// owncloudsql stores versions in the files_versions subfolder -// the incoming path starts with /, so we need to insert the files subfolder into the path -// and prefix the data directory -// TODO the path handed to a storage provider should not contain the username -func (fs *owncloudsqlfs) getVersionsPath(ctx context.Context, ip string) string { - // ip = /path/to/data//files/foo/bar.txt - // remove data dir - if fs.c.DataDirectory != "/" { - // fs.c.DataDirectory is a clean path, so it never ends in / - ip = strings.TrimPrefix(ip, fs.c.DataDirectory) - } - // ip = //files/foo/bar.txt - parts := strings.SplitN(ip, "/", 4) - - // parts[1] contains the username or userid. - u, err := fs.getUser(ctx, parts[1]) - if err != nil { - // TODO return invalid internal path? - return "" - } - layout := templates.WithUser(u, fs.c.UserLayout) - - switch len(parts) { - case 3: - // parts = "", "" - return filepath.Join(fs.c.DataDirectory, layout, "files_versions") - case 4: - // parts = "", "", "foo/bar.txt" - return filepath.Join(fs.c.DataDirectory, layout, "files_versions", parts[3]) - default: - return "" // TODO Must not happen? - } - -} - -// owncloudsql stores trashed items in the files_trashbin subfolder of a users home -func (fs *owncloudsqlfs) getRecyclePath(ctx context.Context) (string, error) { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") - return "", err - } - layout := templates.WithUser(u, fs.c.UserLayout) - return fs.getRecyclePathForUser(layout) -} - -func (fs *owncloudsqlfs) getRecyclePathForUser(user string) (string, error) { - return filepath.Join(fs.c.DataDirectory, user, "files_trashbin/files"), nil -} - -func (fs *owncloudsqlfs) getVersionRecyclePath(ctx context.Context) (string, error) { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") - return "", err - } - layout := templates.WithUser(u, fs.c.UserLayout) - return filepath.Join(fs.c.DataDirectory, layout, "files_trashbin/versions"), nil -} - -func (fs *owncloudsqlfs) toDatabasePath(ip string) string { - owner := fs.getOwner(ip) - trim := filepath.Join(fs.c.DataDirectory, owner) - p := strings.TrimPrefix(ip, trim) - p = strings.TrimPrefix(p, "/") - return p -} - -func (fs *owncloudsqlfs) toResourcePath(ip, owner string) string { - trim := filepath.Join(fs.c.DataDirectory, owner, "files") - p := strings.TrimPrefix(ip, trim) - p = strings.TrimPrefix(p, "/") - // root directory - if p == "" { - p = "." - } - return p -} - -func (fs *owncloudsqlfs) toStoragePath(ctx context.Context, ip string) (sp string) { - if fs.c.EnableHome { - u := ctxpkg.ContextMustGetUser(ctx) - layout := templates.WithUser(u, fs.c.UserLayout) - trim := filepath.Join(fs.c.DataDirectory, layout, "files") - sp = strings.TrimPrefix(ip, trim) - // root directory - if sp == "" { - sp = "/" - } - } else { - // ip = /data//files/foo/bar.txt - // remove data dir - if fs.c.DataDirectory != "/" { - // fs.c.DataDirectory is a clean path, so it never ends in / - ip = strings.TrimPrefix(ip, fs.c.DataDirectory) - // ip = //files/foo/bar.txt - } - - segments := strings.SplitN(ip, "/", 4) - // parts = "", "", "files", "foo/bar.txt" - switch len(segments) { - case 1: - sp = "/" - case 2: - sp = filepath.Join("/", segments[1]) - case 3: - sp = filepath.Join("/", segments[1]) - default: - sp = filepath.Join(segments[1], segments[3]) - } - } - log := appctx.GetLogger(ctx) - log.Debug().Str("driver", "owncloudsql").Str("ipath", ip).Str("spath", sp).Msg("toStoragePath") - return -} - -// TODO the owner needs to come from a different place -func (fs *owncloudsqlfs) getOwner(ip string) string { - ip = strings.TrimPrefix(ip, fs.c.DataDirectory) - parts := strings.SplitN(ip, "/", 3) - if len(parts) > 1 { - return parts[1] - } - return "" -} - -// TODO cache user lookup -func (fs *owncloudsqlfs) getUser(ctx context.Context, usernameOrID string) (id *userpb.User, err error) { - u := ctxpkg.ContextMustGetUser(ctx) - // check if username matches and id is set - if u.Username == usernameOrID && u.Id != nil && u.Id.OpaqueId != "" { - return u, nil - } - // check if userid matches and username is set - if u.Id != nil && u.Id.OpaqueId == usernameOrID && u.Username != "" { - return u, nil - } - // look up at the userprovider - - // parts[0] contains the username or userid. use user service to look up id - c, err := pool.GetUserProviderServiceClient(fs.c.UserProviderEndpoint) - if err != nil { - appctx.GetLogger(ctx). - Error().Err(err). - Str("userprovidersvc", fs.c.UserProviderEndpoint). - Str("usernameOrID", usernameOrID). - Msg("could not get user provider client") - return nil, err - } - res, err := c.GetUser(ctx, &userpb.GetUserRequest{ - UserId: &userpb.UserId{OpaqueId: usernameOrID}, - }) - if err != nil { - appctx.GetLogger(ctx). - Error().Err(err). - Str("userprovidersvc", fs.c.UserProviderEndpoint). - Str("usernameOrID", usernameOrID). - Msg("could not get user") - return nil, err - } - - if res.Status.Code == rpc.Code_CODE_NOT_FOUND { - appctx.GetLogger(ctx). - Error(). - Str("userprovidersvc", fs.c.UserProviderEndpoint). - Str("usernameOrID", usernameOrID). - Interface("status", res.Status). - Msg("user not found by id. Trying by name") - - var cres *userpb.GetUserByClaimResponse - cres, err = c.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ - Claim: "username", - Value: usernameOrID, - }) - if err != nil { - appctx.GetLogger(ctx). - Error().Err(err). - Str("userprovidersvc", fs.c.UserProviderEndpoint). - Str("usernameOrID", usernameOrID). - Msg("could not get user by username") - return nil, err - } - if cres.Status.Code == rpc.Code_CODE_NOT_FOUND { - appctx.GetLogger(ctx). - Error(). - Str("userprovidersvc", fs.c.UserProviderEndpoint). - Str("usernameOrID", usernameOrID). - Interface("status", cres.Status). - Msg("user not found by username") - return nil, fmt.Errorf("user not found") - } - res.User = cres.User - res.Status = cres.Status - } - - if res.Status.Code != rpc.Code_CODE_OK { - appctx.GetLogger(ctx). - Error(). - Str("userprovidersvc", fs.c.UserProviderEndpoint). - Str("usernameOrID", usernameOrID). - Interface("status", res.Status). - Msg("user lookup failed") - return nil, fmt.Errorf("user lookup failed") - } - return res.User, nil -} - -// permissionSet returns the permission set for the current user -func (fs *owncloudsqlfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provider.ResourcePermissions { - if owner == nil { - return &provider.ResourcePermissions{ - Stat: true, - } - } - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - return &provider.ResourcePermissions{ - // no permissions - } - } - if u.Id == nil { - return &provider.ResourcePermissions{ - // no permissions - } - } - if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp { - return &provider.ResourcePermissions{ - // owner has all permissions - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListContainer: true, - ListFileVersions: true, - ListGrants: true, - ListRecycle: true, - Move: true, - PurgeRecycle: true, - RemoveGrant: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - } - } - // TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it - return &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListContainer: true, - ListFileVersions: true, - ListGrants: true, - ListRecycle: true, - Move: true, - PurgeRecycle: true, - RemoveGrant: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - } -} - -func (fs *owncloudsqlfs) getStorage(ctx context.Context, ip string) (int, error) { - return fs.filecache.GetNumericStorageID(ctx, "home::"+fs.getOwner(ip)) -} - -func (fs *owncloudsqlfs) getUserStorage(ctx context.Context, user string) (int, error) { - id, err := fs.filecache.GetNumericStorageID(ctx, "home::"+user) - if err != nil { - id, err = fs.filecache.CreateStorage(ctx, "home::"+user) - } - return id, err -} - -func (fs *owncloudsqlfs) convertToResourceInfo(ctx context.Context, entry *filecache.File, ip string, mdKeys []string) (*provider.ResourceInfo, error) { - mdKeysMap := make(map[string]struct{}) - for _, k := range mdKeys { - mdKeysMap[k] = struct{}{} - } - - var returnAllKeys bool - if _, ok := mdKeysMap["*"]; len(mdKeys) == 0 || ok { - returnAllKeys = true - } - owner := fs.getOwner(ip) - path := fs.toResourcePath(ip, owner) - isDir := entry.MimeTypeString == "httpd/unix-directory" - ri := &provider.ResourceInfo{ - Id: &provider.ResourceId{ - // return ownclouds numeric storage id as the space id! - SpaceId: strconv.Itoa(entry.Storage), OpaqueId: strconv.Itoa(entry.ID), - }, - Path: filepath.Base(path), // we currently only return the name, decomposedfs returns the path if the request was path based. is that even still possible? - Type: getResourceType(isDir), - Etag: entry.Etag, - MimeType: entry.MimeTypeString, - Size: uint64(entry.Size), - Mtime: &types.Timestamp{ - Seconds: uint64(entry.MTime), - }, - ArbitraryMetadata: &provider.ArbitraryMetadata{ - Metadata: map[string]string{}, // TODO aduffeck: which metadata needs to go in here? - }, - } - // omit parentid for root - if path != "." { - ri.Name = entry.Name - ri.ParentId = &provider.ResourceId{ - // return ownclouds numeric storage id as the space id! - SpaceId: strconv.Itoa(entry.Storage), OpaqueId: strconv.Itoa(entry.Parent), - } - } - - if owner, err := fs.getUser(ctx, owner); err == nil { - ri.Owner = owner.Id - } else { - appctx.GetLogger(ctx).Error().Err(err).Msg("error getting owner") - } - - ri.PermissionSet = fs.permissionSet(ctx, ri.Owner) - - // checksums - if !isDir { - if _, checksumRequested := mdKeysMap[checksumsKey]; returnAllKeys || checksumRequested { - // TODO which checksum was requested? sha1 adler32 or md5? for now hardcode sha1? - readChecksumIntoResourceChecksum(ctx, entry.Checksum, storageprovider.XSSHA1, ri) - readChecksumIntoOpaque(ctx, entry.Checksum, storageprovider.XSMD5, ri) - readChecksumIntoOpaque(ctx, ip, storageprovider.XSAdler32, ri) - } - } - - return ri, nil -} - -// GetPathByID returns the storage relative path for the file id, without the internal namespace -func (fs *owncloudsqlfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) { - ip, err := fs.resolve(ctx, &provider.Reference{ResourceId: id}) - if err != nil { - return "", err - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.GetPath { - return "", errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return "", errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return "", errors.Wrap(err, "owncloudsql: error reading permissions") - } - - return fs.toStoragePath(ctx, ip), nil -} - -// resolve takes in a request path or request id and converts it to an internal path. -func (fs *owncloudsqlfs) resolve(ctx context.Context, ref *provider.Reference) (string, error) { - - if ref.GetResourceId() != nil { - p, err := fs.filecache.Path(ctx, ref.GetResourceId().OpaqueId) - if err != nil { - return "", err - } - p = strings.TrimPrefix(p, "files/") - if !fs.c.EnableHome { - owner, err := fs.filecache.GetStorageOwnerByFileID(ctx, ref.GetResourceId().OpaqueId) - if err != nil { - return "", err - } - p = filepath.Join(owner, p) - } - if ref.GetPath() != "" { - p = filepath.Join(p, ref.GetPath()) - } - return fs.toInternalPath(ctx, p), nil - } - - if ref.GetPath() != "" { - return fs.toInternalPath(ctx, ref.GetPath()), nil - } - - // reference is invalid - return "", fmt.Errorf("invalid reference %+v", ref) -} - -func (fs *owncloudsqlfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error { - return errtypes.NotSupported("owncloudsqlfs: deny grant not supported") -} - -func (fs *owncloudsqlfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { - return errtypes.NotSupported("owncloudsqlfs: add grant not supported") -} - -func (fs *owncloudsqlfs) readPermissions(ctx context.Context, ip string) (p *provider.ResourcePermissions, err error) { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - appctx.GetLogger(ctx).Debug().Str("ipath", ip).Msg("no user in context, returning default permissions") - return defaultPermissions, nil - } - // check if the current user is the owner - owner := fs.getOwner(ip) - if owner == u.Username { - appctx.GetLogger(ctx).Debug().Str("ipath", ip).Msg("user is owner, returning owner permissions") - return ownerPermissions, nil - } - - // otherwise this is a share - ownerStorageID, err := fs.filecache.GetNumericStorageID(ctx, "home::"+owner) - if err != nil { - return nil, err - } - entry, err := fs.filecache.Get(ctx, ownerStorageID, fs.toDatabasePath(ip)) - if err != nil { - return nil, err - } - perms, err := conversions.NewPermissions(entry.Permissions) - if err != nil { - return nil, err - } - return conversions.RoleFromOCSPermissions(perms, nil).CS3ResourcePermissions(), nil -} - -// The os not exists error is buried inside the xattr error, -// so we cannot just use os.IsNotExists(). -func isNotFound(err error) bool { - if xerr, ok := err.(*xattr.Error); ok { - if serr, ok2 := xerr.Err.(syscall.Errno); ok2 { - return serr == syscall.ENOENT - } - } - return false -} - -func (fs *owncloudsqlfs) ListGrants(ctx context.Context, ref *provider.Reference) (grants []*provider.Grant, err error) { - return []*provider.Grant{}, nil // nop -} - -func (fs *owncloudsqlfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) { - return nil // nop -} - -func (fs *owncloudsqlfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { - return nil // nop -} - -func (fs *owncloudsqlfs) CreateHome(ctx context.Context) error { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") - return err - } - return fs.createHomeForUser(ctx, templates.WithUser(u, fs.c.UserLayout)) -} - -func (fs *owncloudsqlfs) createHomeForUser(ctx context.Context, user string) error { - homePaths := []string{ - filepath.Join(fs.c.DataDirectory, user), - filepath.Join(fs.c.DataDirectory, user, "files"), - filepath.Join(fs.c.DataDirectory, user, "files_trashbin"), - filepath.Join(fs.c.DataDirectory, user, "files_trashbin/files"), - filepath.Join(fs.c.DataDirectory, user, "files_trashbin/versions"), - filepath.Join(fs.c.DataDirectory, user, "uploads"), - } - - storageID, err := fs.getUserStorage(ctx, user) - if err != nil { - return err - } - for _, v := range homePaths { - if err := os.MkdirAll(v, 0755); err != nil { - return errors.Wrap(err, "owncloudsql: error creating home path: "+v) - } - - fi, err := os.Stat(v) - if err != nil { - return err - } - data := map[string]interface{}{ - "path": fs.toDatabasePath(v), - "etag": calcEtag(ctx, fi), - "mimetype": "httpd/unix-directory", - "permissions": 31, // 1: READ, 2: UPDATE, 4: CREATE, 8: DELETE, 16: SHARE - } - - allowEmptyParent := v == filepath.Join(fs.c.DataDirectory, user) // the root doesn't have a parent - _, err = fs.filecache.InsertOrUpdate(ctx, storageID, data, allowEmptyParent) - if err != nil { - return err - } - } - return nil -} - -// If home is enabled, the relative home is always the empty string -func (fs *owncloudsqlfs) GetHome(ctx context.Context) (string, error) { - if !fs.c.EnableHome { - return "", errtypes.NotSupported("owncloudsql: get home not supported") - } - return "", nil -} - -func (fs *owncloudsqlfs) CreateDir(ctx context.Context, ref *provider.Reference) (_ *storage.CreateDirResult, err error) { - - ip, err := fs.resolve(ctx, ref) - if err != nil { - return nil, err - } - - // check permissions of parent dir - if perm, err := fs.readPermissions(ctx, filepath.Dir(ip)); err == nil { - if !perm.CreateContainer { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.PreconditionFailed(ref.Path) - } - return nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - - if err = os.Mkdir(ip, 0700); err != nil { - if os.IsNotExist(err) { - return nil, errtypes.PreconditionFailed(ref.Path) - } - if os.IsExist(err) { - return nil, errtypes.AlreadyExists(ref.Path) - } - return nil, errors.Wrap(err, "owncloudsql: error creating dir "+fs.toStoragePath(ctx, filepath.Dir(ip))) - } - - fi, err := os.Stat(ip) - if err != nil { - return nil, err - } - mtime := time.Now().Unix() - - permissions := 31 // 1: READ, 2: UPDATE, 4: CREATE, 8: DELETE, 16: SHARE - if perm, err := fs.readPermissions(ctx, filepath.Dir(ip)); err == nil { - permissions = int(conversions.RoleFromResourcePermissions(perm, false).OCSPermissions()) // inherit permissions of parent - } - data := map[string]interface{}{ - "path": fs.toDatabasePath(ip), - "etag": calcEtag(ctx, fi), - "mimetype": "httpd/unix-directory", - "permissions": permissions, - "mtime": mtime, - "storage_mtime": mtime, - } - storageID, err := fs.getStorage(ctx, ip) - if err != nil { - return nil, err - } - _, err = fs.filecache.InsertOrUpdate(ctx, storageID, data, false) - if err != nil { - return nil, err - } - - if err := fs.propagate(ctx, filepath.Dir(ip)); err != nil { - return nil, err - } - return &storage.CreateDirResult{}, nil -} - -// TouchFile as defined in the storage.FS interface -func (fs *owncloudsqlfs) TouchFile(ctx context.Context, ref *provider.Reference, markprocessing bool, mtime string) (*storage.TouchFileResult, error) { - ip, err := fs.resolve(ctx, ref) - if err != nil { - return nil, err - } - - // check permissions of parent dir - parentPerms, err := fs.readPermissions(ctx, filepath.Dir(ip)) - if err == nil { - if !parentPerms.InitiateFileUpload { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(ref.Path) - } - return nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - - _, err = os.Create(ip) - if err != nil { - if os.IsNotExist(err) { - return nil, errtypes.NotFound(ref.Path) - } - // FIXME we also need already exists error, webdav expects 405 MethodNotAllowed - return nil, errors.Wrap(err, "owncloudsql: error creating file "+fs.toStoragePath(ctx, filepath.Dir(ip))) - } - - if err = os.Chmod(ip, 0700); err != nil { - return nil, errors.Wrap(err, "owncloudsql: error setting file permissions on "+fs.toStoragePath(ctx, filepath.Dir(ip))) - } - - fi, err := os.Stat(ip) - if err != nil { - return nil, err - } - storageMtime := time.Now().Unix() - mt := storageMtime - if mtime != "" { - t, err := strconv.Atoi(mtime) - if err != nil { - log.Info(). - Str("owncloudsql", ip). - Msg("error mtime conversion. mtine set to system time") - } - mt = time.Unix(int64(t), 0).Unix() - } - - data := map[string]interface{}{ - "path": fs.toDatabasePath(ip), - "etag": calcEtag(ctx, fi), - "mimetype": mime.Detect(false, ip), - "permissions": int(conversions.RoleFromResourcePermissions(parentPerms, false).OCSPermissions()), // inherit permissions of parent - "mtime": mt, - "storage_mtime": storageMtime, - } - storageID, err := fs.getStorage(ctx, ip) - if err != nil { - return nil, err - } - _, err = fs.filecache.InsertOrUpdate(ctx, storageID, data, false) - if err != nil { - return nil, err - } - - if err := fs.propagate(ctx, filepath.Dir(ip)); err != nil { - return nil, err - } - return &storage.TouchFileResult{}, nil -} - -func (fs *owncloudsqlfs) CreateReference(ctx context.Context, sp string, targetURI *url.URL) error { - return errtypes.NotSupported("owncloudsql: operation not supported") -} - -func (fs *owncloudsqlfs) setMtime(ctx context.Context, ip string, mtime string) error { - log := appctx.GetLogger(ctx) - if mt, err := parseMTime(mtime); err == nil { - // updating mtime also updates atime - if err := os.Chtimes(ip, mt, mt); err != nil { - log.Error().Err(err). - Str("ipath", ip). - Time("mtime", mt). - Msg("could not set mtime") - return errors.Wrap(err, "could not set mtime") - } - } else { - log.Error().Err(err). - Str("ipath", ip). - Str("mtime", mtime). - Msg("could not parse mtime") - return errors.Wrap(err, "could not parse mtime") - } - return nil -} -func (fs *owncloudsqlfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) (err error) { - log := appctx.GetLogger(ctx) - - var ip string - if ip, err = fs.resolve(ctx, ref); err != nil { - return errors.Wrap(err, "owncloudsql: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.InitiateFileUpload { // TODO add dedicated permission? - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return errors.Wrap(err, "owncloudsql: error reading permissions") - } - - var fi os.FileInfo - fi, err = os.Stat(ip) - if err != nil { - if os.IsNotExist(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return errors.Wrap(err, "owncloudsql: error stating "+ip) - } - - errs := []error{} - - if md.Metadata != nil { - if val, ok := md.Metadata["mtime"]; ok { - err := fs.setMtime(ctx, ip, val) - if err != nil { - errs = append(errs, errors.Wrap(err, "could not set mtime")) - } - // remove from metadata - delete(md.Metadata, "mtime") - } - // TODO(jfd) special handling for atime? - // TODO(jfd) allow setting birth time (btime)? - // TODO(jfd) any other metadata that is interesting? fileid? - if val, ok := md.Metadata["etag"]; ok { - etag := calcEtag(ctx, fi) - val = fmt.Sprintf("\"%s\"", strings.Trim(val, "\"")) - if etag == val { - log.Debug(). - Str("ipath", ip). - Str("etag", val). - Msg("ignoring request to update identical etag") - } else - // etag is only valid until the calculated etag changes - // TODO(jfd) cleanup in a batch job - if err := xattr.Set(ip, etagPrefix+etag, []byte(val)); err != nil { - log.Error().Err(err). - Str("ipath", ip). - Str("calcetag", etag). - Str("etag", val). - Msg("could not set etag") - errs = append(errs, errors.Wrap(err, "could not set etag")) - } - delete(md.Metadata, "etag") - } - if val, ok := md.Metadata["http://owncloud.org/ns/favorite"]; ok { - // TODO we should not mess with the user here ... the favorites is now a user specific property for a file - // that cannot be mapped to extended attributes without leaking who has marked a file as a favorite - // it is a specific case of a tag, which is user individual as well - // TODO there are different types of tags - // 1. public that are managed by everyone - // 2. private tags that are only visible to the user - // 3. system tags that are only visible to the system - // 4. group tags that are only visible to a group ... - // urgh ... well this can be solved using different namespaces - // 1. public = p: - // 2. private = u:: for user specific - // 3. system = s: for system - // 4. group = g:: - // 5. app? = a:: for apps? - // obviously this only is secure when the u/s/g/a namespaces are not accessible by users in the filesystem - // public tags can be mapped to extended attributes - if u, ok := ctxpkg.ContextGetUser(ctx); ok { - // the favorite flag is specific to the user, so we need to incorporate the userid - if uid := u.GetId(); uid != nil { - fa := fmt.Sprintf("%s%s@%s", favPrefix, uid.GetOpaqueId(), uid.GetIdp()) - if err := xattr.Set(ip, fa, []byte(val)); err != nil { - log.Error().Err(err). - Str("ipath", ip). - Interface("user", u). - Str("key", fa). - Msg("could not set favorite flag") - errs = append(errs, errors.Wrap(err, "could not set favorite flag")) - } - } else { - log.Error(). - Str("ipath", ip). - Interface("user", u). - Msg("user has no id") - errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id")) - } - } else { - log.Error(). - Str("ipath", ip). - Interface("user", u). - Msg("error getting user from ctx") - errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx")) - } - // remove from metadata - delete(md.Metadata, "http://owncloud.org/ns/favorite") - } - } - for k, v := range md.Metadata { - if err := xattr.Set(ip, mdPrefix+k, []byte(v)); err != nil { - log.Error().Err(err). - Str("ipath", ip). - Str("key", k). - Str("val", v). - Msg("could not set metadata") - errs = append(errs, errors.Wrap(err, "could not set metadata")) - } - } - switch len(errs) { - case 0: - return fs.propagate(ctx, ip) - case 1: - return errs[0] - default: - // TODO how to return multiple errors? - return errors.New("multiple errors occurred, see log for details") - } -} - -func parseMTime(v string) (t time.Time, err error) { - p := strings.SplitN(v, ".", 2) - var sec, nsec int64 - if sec, err = strconv.ParseInt(p[0], 10, 64); err == nil { - if len(p) > 1 { - nsec, err = strconv.ParseInt(p[1], 10, 64) - } - } - return time.Unix(sec, nsec), err -} - -func (fs *owncloudsqlfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) (err error) { - log := appctx.GetLogger(ctx) - - var ip string - if ip, err = fs.resolve(ctx, ref); err != nil { - return errors.Wrap(err, "owncloudsql: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.InitiateFileUpload { // TODO add dedicated permission? - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return errors.Wrap(err, "owncloudsql: error reading permissions") - } - - _, err = os.Stat(ip) - if err != nil { - if os.IsNotExist(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return errors.Wrap(err, "owncloudsql: error stating "+ip) - } - - errs := []error{} - for _, k := range keys { - switch k { - case "http://owncloud.org/ns/favorite": - if u, ok := ctxpkg.ContextGetUser(ctx); ok { - // the favorite flag is specific to the user, so we need to incorporate the userid - if uid := u.GetId(); uid != nil { - fa := fmt.Sprintf("%s%s@%s", favPrefix, uid.GetOpaqueId(), uid.GetIdp()) - if err := xattr.Remove(ip, fa); err != nil { - log.Error().Err(err). - Str("ipath", ip). - Interface("user", u). - Str("key", fa). - Msg("could not unset favorite flag") - errs = append(errs, errors.Wrap(err, "could not unset favorite flag")) - } - } else { - log.Error(). - Str("ipath", ip). - Interface("user", u). - Msg("user has no id") - errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id")) - } - } else { - log.Error(). - Str("ipath", ip). - Interface("user", u). - Msg("error getting user from ctx") - errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx")) - } - default: - if err = xattr.Remove(ip, mdPrefix+k); err != nil { - // a non-existing attribute will return an error, which we can ignore - // (using string compare because the error type is syscall.Errno and not wrapped/recognizable) - if e, ok := err.(*xattr.Error); !ok || !(e.Err.Error() == "no data available" || - // darwin - e.Err.Error() == "attribute not found") { - log.Error().Err(err). - Str("ipath", ip). - Str("key", k). - Msg("could not unset metadata") - errs = append(errs, errors.Wrap(err, "could not unset metadata")) - } - } - } - } - - switch len(errs) { - case 0: - return fs.propagate(ctx, ip) - case 1: - return errs[0] - default: - // TODO how to return multiple errors? - return errors.New("multiple errors occurred, see log for details") - } -} - -// GetLock returns an existing lock on the given reference -func (fs *owncloudsqlfs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) { - return nil, errtypes.NotSupported("unimplemented") -} - -// SetLock puts a lock on the given reference -func (fs *owncloudsqlfs) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) (*storage.SetLockResult, error) { - return nil, errtypes.NotSupported("unimplemented") -} - -// RefreshLock refreshes an existing lock on the given reference -func (fs *owncloudsqlfs) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error { - return errtypes.NotSupported("unimplemented") -} - -// Unlock removes an existing lock from the given reference -func (fs *owncloudsqlfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) (*storage.UnlockResult, error) { - return nil, errtypes.NotSupported("unimplemented") -} - -// Delete is actually only a move to trash -// -// This is a first optimistic approach. -// When a file has versions and we want to delete the file it could happen that -// the service crashes before all moves are finished. -// That would result in invalid state like the main files was moved but the -// versions were not. -// We will live with that compromise since this storage driver will be -// deprecated soon. -func (fs *owncloudsqlfs) Delete(ctx context.Context, ref *provider.Reference) (_ *storage.DeleteResult, err error) { - var ip string - if ip, err = fs.resolve(ctx, ref); err != nil { - return nil, errors.Wrap(err, "owncloudsql: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.Delete { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - - _, err = os.Stat(ip) - if err != nil { - if os.IsNotExist(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return nil, errors.Wrap(err, "owncloudsql: error stating "+ip) - } - - // Delete file into the owner's trash, not the user's (in case of shares) - rp, err := fs.getRecyclePathForUser(fs.getOwner(ip)) - if err != nil { - return nil, errors.Wrap(err, "owncloudsql: error resolving recycle path") - } - - if err := os.MkdirAll(rp, 0700); err != nil { - return nil, errors.Wrap(err, "owncloudsql: error creating trashbin dir "+rp) - } - - // ip is the path on disk ... we need only the path relative to root - origin := filepath.Dir(fs.toStoragePath(ctx, ip)) - - err = fs.trash(ctx, ip, rp, origin) - if err != nil { - return nil, errors.Wrapf(err, "owncloudsql: error deleting file %s", ip) - } - return &storage.DeleteResult{}, nil -} - -func (fs *owncloudsqlfs) trash(ctx context.Context, ip string, rp string, origin string) error { - // move to trash location - dtime := time.Now().Unix() - tgt := filepath.Join(rp, fmt.Sprintf("%s.d%d", filepath.Base(ip), dtime)) - if err := os.Rename(ip, tgt); err != nil { - if os.IsExist(err) { - // timestamp collision, try again with higher value: - dtime++ - tgt := filepath.Join(rp, fmt.Sprintf("%s.d%d", filepath.Base(ip), dtime)) - if err := os.Rename(ip, tgt); err != nil { - return errors.Wrap(err, "owncloudsql: could not move item to trash") - } - } - } - - storage, err := fs.getStorage(ctx, ip) - if err != nil { - return err - } - - tryDelete := func() error { - return fs.filecache.Delete(ctx, storage, fs.getOwner(ip), fs.toDatabasePath(ip), fs.toDatabasePath(tgt)) - } - err = tryDelete() - if err != nil { - err = fs.createHomeForUser(ctx, fs.getOwner(ip)) // Try setting up the owner's home (incl. trash) to fix the problem - if err != nil { - return err - } - err = tryDelete() - if err != nil { - return err - } - } - - err = fs.trashVersions(ctx, ip, origin, dtime) - if err != nil { - return errors.Wrapf(err, "owncloudsql: error deleting versions of file %s", ip) - } - - return fs.propagate(ctx, filepath.Dir(ip)) -} - -func (fs *owncloudsqlfs) trashVersions(ctx context.Context, ip string, origin string, dtime int64) error { - vp := fs.getVersionsPath(ctx, ip) - vrp, err := fs.getVersionRecyclePath(ctx) - if err != nil { - return errors.Wrap(err, "error resolving versions recycle path") - } - - if err := os.MkdirAll(vrp, 0700); err != nil { - return errors.Wrap(err, "owncloudsql: error creating trashbin dir "+vrp) - } - - // Ignore error since the only possible error is malformed pattern. - versions, _ := filepath.Glob(vp + ".v*") - storage, err := fs.getStorage(ctx, ip) - if err != nil { - return err - } - for _, v := range versions { - tgt := filepath.Join(vrp, fmt.Sprintf("%s.d%d", filepath.Base(v), dtime)) - if err := os.Rename(v, tgt); err != nil { - if os.IsExist(err) { - // timestamp collision, try again with higher value: - dtime++ - tgt := filepath.Join(vrp, fmt.Sprintf("%s.d%d", filepath.Base(ip), dtime)) - if err := os.Rename(ip, tgt); err != nil { - return errors.Wrap(err, "owncloudsql: could not move item to trash") - } - } - } - if err != nil { - return errors.Wrap(err, "owncloudsql: error deleting file "+v) - } - err = fs.filecache.Move(ctx, storage, fs.toDatabasePath(v), fs.toDatabasePath(tgt)) - if err != nil { - return errors.Wrap(err, "owncloudsql: error deleting file "+v) - } - } - return nil -} - -func (fs *owncloudsqlfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) (*storage.MoveResult, error) { - oldIP, err := fs.resolve(ctx, oldRef) - if err != nil { - return nil, errors.Wrap(err, "owncloudsql: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, oldIP); err == nil { - if !perm.Move { // TODO add dedicated permission? - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(oldIP))) - } - return nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - - newIP, err := fs.resolve(ctx, newRef) - if err != nil { - return nil, errors.Wrap(err, "owncloudsql: error resolving reference") - } - - // TODO check target permissions ... if it exists - stor, err := fs.getStorage(ctx, oldIP) - if err != nil { - return nil, err - } - err = fs.filecache.Move(ctx, stor, fs.toDatabasePath(oldIP), fs.toDatabasePath(newIP)) - if err != nil { - return nil, err - } - if err = os.Rename(oldIP, newIP); err != nil { - return nil, errors.Wrap(err, "owncloudsql: error moving "+oldIP+" to "+newIP) - } - - if err := fs.propagate(ctx, newIP); err != nil { - return nil, err - } - if filepath.Dir(newIP) != filepath.Dir(oldIP) { - if err := fs.propagate(ctx, filepath.Dir(oldIP)); err != nil { - return nil, err - } - } - return &storage.MoveResult{}, nil -} - -func (fs *owncloudsqlfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string, fieldMask []string) (*provider.ResourceInfo, error) { - ip, err := fs.resolve(ctx, ref) - if err != nil { - // TODO return correct errtype - if _, ok := err.(errtypes.IsNotFound); ok { - return nil, err - } - return nil, errors.Wrap(err, "owncloudsql: error resolving reference") - } - p := fs.toStoragePath(ctx, ip) - - // If GetMD is called for a path shared with the user then the path is - // already wrapped. (fs.resolve wraps the path) - if strings.HasPrefix(p, fs.c.DataDirectory) { - ip = p - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.Stat { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - - ownerStorageID, err := fs.filecache.GetNumericStorageID(ctx, "home::"+fs.getOwner(ip)) - if err != nil { - return nil, err - } - entry, err := fs.filecache.Get(ctx, ownerStorageID, fs.toDatabasePath(ip)) - switch { - case err == sql.ErrNoRows: - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - case err != nil: - return nil, err - } - - return fs.convertToResourceInfo(ctx, entry, ip, mdKeys) -} - -func (fs *owncloudsqlfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) { - log := appctx.GetLogger(ctx) - - ip, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "owncloudsql: error resolving reference") - } - sp := fs.toStoragePath(ctx, ip) - - if fs.c.EnableHome { - log.Debug().Msg("home enabled") - if strings.HasPrefix(sp, "/") { - // permissions checked in listWithHome - return fs.listWithHome(ctx, "/", sp, mdKeys) - } - } - - log.Debug().Msg("list with nominal home") - // permissions checked in listWithNominalHome - return fs.listWithNominalHome(ctx, sp, mdKeys) -} - -func (fs *owncloudsqlfs) listWithNominalHome(ctx context.Context, ip string, mdKeys []string) ([]*provider.ResourceInfo, error) { - - // If a user wants to list a folder shared with him the path will already - // be wrapped with the files directory path of the share owner. - // In that case we don't want to wrap the path again. - if !strings.HasPrefix(ip, fs.c.DataDirectory) { - ip = fs.toInternalPath(ctx, ip) - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListContainer { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - - storage, err := fs.getStorage(ctx, ip) - if err != nil { - return nil, err - } - entries, err := fs.filecache.List(ctx, storage, fs.toDatabasePath(ip)+"/") - if err != nil { - return nil, errors.Wrapf(err, "owncloudsql: error listing %s", ip) - } - owner := fs.getOwner(ip) - finfos := []*provider.ResourceInfo{} - for _, entry := range entries { - cp := filepath.Join(fs.c.DataDirectory, owner, entry.Path) - m, err := fs.convertToResourceInfo(ctx, entry, cp, mdKeys) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("path", cp).Msg("error converting to a resource info") - } - finfos = append(finfos, m) - } - return finfos, nil -} - -func (fs *owncloudsqlfs) listWithHome(ctx context.Context, home, p string, mdKeys []string) ([]*provider.ResourceInfo, error) { - log := appctx.GetLogger(ctx) - if p == home { - log.Debug().Msg("listing home") - return fs.listHome(ctx, home, mdKeys) - } - - log.Debug().Msg("listing nominal home") - return fs.listWithNominalHome(ctx, p, mdKeys) -} - -func (fs *owncloudsqlfs) listHome(ctx context.Context, home string, mdKeys []string) ([]*provider.ResourceInfo, error) { - // list files - ip := fs.toInternalPath(ctx, home) - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListContainer { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - - storage, err := fs.getStorage(ctx, ip) - if err != nil { - return nil, err - } - entries, err := fs.filecache.List(ctx, storage, fs.toDatabasePath(ip)+"/") - if err != nil { - return nil, errors.Wrapf(err, "owncloudsql: error listing %s", ip) - } - owner := fs.getOwner(ip) - finfos := []*provider.ResourceInfo{} - for _, entry := range entries { - cp := filepath.Join(fs.c.DataDirectory, owner, entry.Path) - m, err := fs.convertToResourceInfo(ctx, entry, cp, mdKeys) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("path", cp).Msg("error converting to a resource info") - } - finfos = append(finfos, m) - } - return finfos, nil -} - -func (fs *owncloudsqlfs) archiveRevision(ctx context.Context, vbp string, ip string) error { - // move existing file to versions dir - vp := fmt.Sprintf("%s.v%d", vbp, time.Now().Unix()) - if err := os.MkdirAll(filepath.Dir(vp), 0700); err != nil { - return errors.Wrap(err, "owncloudsql: error creating versions dir "+vp) - } - - // TODO(jfd): make sure rename is atomic, missing fsync ... - if err := os.Rename(ip, vp); err != nil { - return errors.Wrap(err, "owncloudsql: error renaming from "+ip+" to "+vp) - } - - storage, err := fs.getStorage(ctx, ip) - if err != nil { - return err - } - - vdp := fs.toDatabasePath(vp) - basePath := strings.TrimSuffix(vp, vdp) - parts := strings.Split(filepath.Dir(vdp), "/") - walkPath := "" - for i := 0; i < len(parts); i++ { - walkPath = filepath.Join(walkPath, parts[i]) - _, err := fs.filecache.Get(ctx, storage, walkPath) - if err == nil { - continue - } - - fi, err := os.Stat(filepath.Join(basePath, walkPath)) - if err != nil { - return errors.Wrap(err, "could not stat parent version directory") - } - data := map[string]interface{}{ - "path": walkPath, - "mimetype": "httpd/unix-directory", - "etag": calcEtag(ctx, fi), - "permissions": 31, // 1: READ, 2: UPDATE, 4: CREATE, 8: DELETE, 16: SHARE - } - - _, err = fs.filecache.InsertOrUpdate(ctx, storage, data, false) - if err != nil { - return errors.Wrap(err, "could not create parent version directory") - } - } - _, err = fs.filecache.Copy(ctx, storage, fs.toDatabasePath(ip), vdp) - return err -} - -func (fs *owncloudsqlfs) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) { - ip, err := fs.resolve(ctx, ref) - if err != nil { - return nil, nil, errors.Wrap(err, "owncloudsql: error resolving reference") - } - p := fs.toStoragePath(ctx, ip) - - // If Download is called for a path shared with the user then the path is - // already wrapped. (fs.resolve wraps the path) - if strings.HasPrefix(p, fs.c.DataDirectory) { - ip = p - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.InitiateFileDownload { - return nil, nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - - ownerStorageID, err := fs.filecache.GetNumericStorageID(ctx, "home::"+fs.getOwner(ip)) - if err != nil { - return nil, nil, err - } - entry, err := fs.filecache.Get(ctx, ownerStorageID, fs.toDatabasePath(ip)) - switch { - case err == sql.ErrNoRows: - return nil, nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - case err != nil: - return nil, nil, err - } - - md, err := fs.convertToResourceInfo(ctx, entry, ip, nil) - if err != nil { - return nil, nil, err - } - - if !openReaderfunc(md) { - return md, nil, nil - } - - r, err := os.Open(ip) - if err != nil { - if os.IsNotExist(err) { - return nil, nil, errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return nil, nil, errors.Wrap(err, "owncloudsql: error reading "+ip) - } - return md, r, nil -} - -func (fs *owncloudsqlfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { - ip, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "owncloudsql: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListFileVersions { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - - vp := fs.getVersionsPath(ctx, ip) - bn := filepath.Base(ip) - storageID, err := fs.getStorage(ctx, ip) - if err != nil { - return nil, err - } - entries, err := fs.filecache.List(ctx, storageID, filepath.Dir(fs.toDatabasePath(vp))+"/") - if err != nil { - return nil, err - } - revisions := []*provider.FileVersion{} - for _, entry := range entries { - if strings.HasPrefix(entry.Name, bn) { - // versions have filename.ext.v12345678 - version := entry.Name[len(bn)+2:] // truncate ".v" to get version mtime - mtime, err := strconv.Atoi(version) - if err != nil { - log := appctx.GetLogger(ctx) - log.Error().Err(err).Str("path", entry.Name).Msg("invalid version mtime") - return nil, err - } - revisions = append(revisions, &provider.FileVersion{ - Key: version, - Size: uint64(entry.Size), - Mtime: uint64(mtime), - Etag: entry.Etag, - }) - } - } - - return revisions, nil -} - -func (fs *owncloudsqlfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) { - return nil, nil, errtypes.NotSupported("download revision") -} - -func (fs *owncloudsqlfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (*storage.RestoreRevisionResult, error) { - ip, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "owncloudsql: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.RestoreFileVersion { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - - vp := fs.getVersionsPath(ctx, ip) - rp := vp + ".v" + revisionKey - - // check revision exists - rs, err := os.Stat(rp) - if err != nil { - return nil, err - } - - if !rs.Mode().IsRegular() { - return nil, fmt.Errorf("%s is not a regular file", rp) - } - - source, err := os.Open(rp) - if err != nil { - return nil, err - } - defer source.Close() - - // destination should be available, otherwise we could not have navigated to its revisions - if err := fs.archiveRevision(ctx, fs.getVersionsPath(ctx, ip), ip); err != nil { - return nil, err - } - - destination, err := os.Create(ip) - if err != nil { - // TODO(jfd) bring back revision in case sth goes wrong? - return nil, err - } - defer destination.Close() - - _, err = io.Copy(destination, source) - if err != nil { - return nil, err - } - - sha1h, md5h, adler32h, err := fs.HashFile(ip) - if err != nil { - log.Err(err).Msg("owncloudsql: could not open file for checksumming") - } - fi, err := os.Stat(ip) - if err != nil { - return nil, err - } - mtime := time.Now().Unix() - data := map[string]interface{}{ - "path": fs.toDatabasePath(ip), - "checksum": fmt.Sprintf("SHA1:%032x MD5:%032x ADLER32:%032x", sha1h, md5h, adler32h), - "etag": calcEtag(ctx, fi), - "size": fi.Size(), - "mimetype": mime.Detect(false, ip), - "mtime": mtime, - "storage_mtime": mtime, - } - storageID, err := fs.getStorage(ctx, ip) - if err != nil { - return nil, err - } - _, err = fs.filecache.InsertOrUpdate(ctx, storageID, data, false) - if err != nil { - return nil, err - } - - // TODO(jfd) bring back revision in case sth goes wrong? - if err := fs.propagate(ctx, ip); err != nil { - return nil, err - } - return &storage.RestoreRevisionResult{}, nil -} - -func (fs *owncloudsqlfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error { - rp, err := fs.getRecyclePath(ctx) - if err != nil { - return errors.Wrap(err, "owncloudsql: error resolving recycle path") - } - vp := filepath.Join(filepath.Dir(rp), "versions") - ip := filepath.Join(rp, filepath.Clean(key)) - // TODO check permission? - - // check permissions - /* are they stored in the trash? - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListContainer { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.unwrap(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "owncloudsql: error reading permissions") - } - */ - - err = os.RemoveAll(ip) - if err != nil { - return errors.Wrap(err, "owncloudsql: error deleting recycle item") - } - base, ttime, err := splitTrashKey(key) - if err != nil { - return err - } - err = fs.filecache.PurgeRecycleItem(ctx, ctxpkg.ContextMustGetUser(ctx).Username, base, ttime, false) - if err != nil { - return err - } - - versionsGlob := filepath.Join(vp, base+".v*.d"+strconv.Itoa(ttime)) - versionFiles, err := filepath.Glob(versionsGlob) - if err != nil { - return errors.Wrap(err, "owncloudsql: error listing recycle item versions") - } - storageID, err := fs.getStorage(ctx, ip) - if err != nil { - return err - } - for _, versionFile := range versionFiles { - err = os.Remove(versionFile) - if err != nil { - return errors.Wrap(err, "owncloudsql: error deleting recycle item versions") - } - err = fs.filecache.Purge(ctx, storageID, fs.toDatabasePath(versionFile)) - if err != nil { - return err - } - } - - // TODO delete keyfiles, keys, share-keys - return nil -} - -func (fs *owncloudsqlfs) EmptyRecycle(ctx context.Context, ref *provider.Reference) error { - // TODO check permission? on what? user must be the owner - rp, err := fs.getRecyclePath(ctx) - if err != nil { - return errors.Wrap(err, "owncloudsql: error resolving recycle path") - } - err = os.RemoveAll(rp) - if err != nil { - return errors.Wrap(err, "owncloudsql: error deleting recycle files") - } - err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions")) - if err != nil { - return errors.Wrap(err, "owncloudsql: error deleting recycle files versions") - } - - u := ctxpkg.ContextMustGetUser(ctx) - err = fs.filecache.EmptyRecycle(ctx, u.Username) - if err != nil { - return errors.Wrap(err, "owncloudsql: error deleting recycle items from the database") - } - - // TODO delete keyfiles, keys, share-keys ... or just everything? - return nil -} - -func splitTrashKey(key string) (string, int, error) { - // trashbin items have filename.ext.d12345678 - suffix := filepath.Ext(key) - if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { - return "", -1, fmt.Errorf("invalid suffix") - } - trashtime := suffix[2:] // truncate "d" to get trashbin time - ttime, err := strconv.Atoi(trashtime) - if err != nil { - return "", -1, fmt.Errorf("invalid suffix") - } - return strings.TrimSuffix(filepath.Base(key), suffix), ttime, nil -} - -func (fs *owncloudsqlfs) convertToRecycleItem(ctx context.Context, md os.FileInfo) *provider.RecycleItem { - base, ttime, err := splitTrashKey(md.Name()) - if err != nil { - log := appctx.GetLogger(ctx) - log.Error().Str("path", md.Name()).Msg("invalid trash item key") - } - - u := ctxpkg.ContextMustGetUser(ctx) - item, err := fs.filecache.GetRecycleItem(ctx, u.Username, base, ttime) - if err != nil { - log := appctx.GetLogger(ctx) - log.Error().Err(err).Str("path", md.Name()).Msg("could not get trash item") - return nil - } - - // ownCloud 10 stores the parent dir of the deleted item as the location in the oc_files_trashbin table - // we use extended attributes for original location, but also only the parent location, which is why - // we need to join and trim the path when listing it - originalPath := filepath.Join(item.Path, base) - - return &provider.RecycleItem{ - Type: getResourceType(md.IsDir()), - Key: md.Name(), - // TODO do we need to prefix the path? it should be relative to this storage root, right? - Ref: &provider.Reference{Path: originalPath}, - Size: uint64(md.Size()), - DeletionTime: &types.Timestamp{ - Seconds: uint64(ttime), - // no nanos available - }, - } -} - -func (fs *owncloudsqlfs) ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error) { - // TODO check permission? on what? user must be the owner? - rp, err := fs.getRecyclePath(ctx) - if err != nil { - return nil, errors.Wrap(err, "owncloudsql: error resolving recycle path") - } - - // list files folder - mds, err := os.ReadDir(rp) - if err != nil { - log := appctx.GetLogger(ctx) - log.Debug().Err(err).Str("path", rp).Msg("trash not readable") - // TODO jfd only ignore not found errors - return []*provider.RecycleItem{}, nil - } - // TODO (jfd) limit and offset - items := []*provider.RecycleItem{} - for i := range mds { - mdsInfo, _ := mds[i].Info() - ri := fs.convertToRecycleItem(ctx, mdsInfo) - if ri != nil { - items = append(items, ri) - } - - } - return items, nil -} - -func (fs *owncloudsqlfs) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) (*storage.RestoreRecycleItemResult, error) { - log := appctx.GetLogger(ctx) - - base, ttime, err := splitTrashKey(key) - if err != nil { - log.Error().Str("path", key).Msg("invalid trash item key") - return nil, fmt.Errorf("invalid trash item suffix") - } - - recyclePath, err := fs.getRecyclePath(ctx) - if err != nil { - return nil, errors.Wrap(err, "owncloudsql: error resolving recycle path") - } - src := filepath.Join(recyclePath, filepath.Clean(key)) - - if restoreRef.Path == "" { - u := ctxpkg.ContextMustGetUser(ctx) - item, err := fs.filecache.GetRecycleItem(ctx, u.Username, base, ttime) - if err != nil { - log := appctx.GetLogger(ctx) - log.Error().Err(err).Str("path", key).Msg("could not get trash item") - return nil, err - } - restoreRef.Path = filepath.Join(item.Path, item.Name) - } - - tgt := fs.toInternalPath(ctx, restoreRef.Path) - // move back to original location - if err := os.Rename(src, tgt); err != nil { - log.Error().Err(err).Str("key", key).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") - return nil, errors.Wrap(err, "owncloudsql: could not restore item") - } - - storageID, err := fs.getStorage(ctx, src) - if err != nil { - return nil, err - } - err = fs.filecache.Move(ctx, storageID, fs.toDatabasePath(src), fs.toDatabasePath(tgt)) - if err != nil { - return nil, err - } - err = fs.filecache.DeleteRecycleItem(ctx, ctxpkg.ContextMustGetUser(ctx).Username, base, ttime) - if err != nil { - return nil, err - } - err = fs.RestoreRecycleItemVersions(ctx, key, tgt) - if err != nil { - return nil, err - } - - if err := fs.propagate(ctx, tgt); err != nil { - return nil, err - } - return &storage.RestoreRecycleItemResult{}, nil -} - -func (fs *owncloudsqlfs) RestoreRecycleItemVersions(ctx context.Context, key, target string) error { - base, ttime, err := splitTrashKey(key) - if err != nil { - return fmt.Errorf("invalid trash item suffix") - } - storage, err := fs.getStorage(ctx, target) - if err != nil { - return err - } - - recyclePath, err := fs.getRecyclePath(ctx) - if err != nil { - return errors.Wrap(err, "owncloudsql: error resolving recycle path") - } - versionsRecyclePath := filepath.Join(filepath.Dir(recyclePath), "versions") - - // Restore versions - deleteSuffix := ".d" + strconv.Itoa(ttime) - versionsGlob := filepath.Join(versionsRecyclePath, base+".v*"+deleteSuffix) - versionFiles, err := filepath.Glob(versionsGlob) - versionsRoot := filepath.Dir(fs.getVersionsPath(ctx, target)) - - if err != nil { - return errors.Wrap(err, "owncloudsql: error listing recycle item versions") - } - for _, versionFile := range versionFiles { - versionBase := strings.TrimSuffix(filepath.Base(versionFile), deleteSuffix) - versionsRestorePath := filepath.Join(versionsRoot, versionBase) - if err = os.Rename(versionFile, versionsRestorePath); err != nil { - return errors.Wrap(err, "owncloudsql: could not restore version file") - } - err = fs.filecache.Move(ctx, storage, fs.toDatabasePath(versionFile), fs.toDatabasePath(versionsRestorePath)) - if err != nil { - return err - } - } - return nil -} - -func (fs *owncloudsqlfs) propagate(ctx context.Context, leafPath string) error { - var root string - if fs.c.EnableHome { - root = filepath.Clean(fs.toInternalPath(ctx, "/")) - } else { - owner := fs.getOwner(leafPath) - root = filepath.Clean(fs.toInternalPath(ctx, owner)) - } - versionsRoot := filepath.Join(filepath.Dir(root), "files_versions") - if !strings.HasPrefix(leafPath, root) { - err := errors.New("internal path outside root") - appctx.GetLogger(ctx).Error(). - Err(err). - Str("leafPath", leafPath). - Str("root", root). - Msg("could not propagate change") - return err - } - - fi, err := os.Stat(leafPath) - if err != nil { - appctx.GetLogger(ctx).Error(). - Err(err). - Str("leafPath", leafPath). - Str("root", root). - Msg("could not propagate change") - return err - } - - storageID, err := fs.getStorage(ctx, leafPath) - if err != nil { - return err - } - - currentPath := filepath.Clean(leafPath) - for currentPath != root && currentPath != versionsRoot { - appctx.GetLogger(ctx).Debug(). - Str("leafPath", leafPath). - Str("currentPath", currentPath). - Msg("propagating change") - parentFi, err := os.Stat(currentPath) - if err != nil { - return err - } - if fi.ModTime().UnixNano() > parentFi.ModTime().UnixNano() { - if err := os.Chtimes(currentPath, fi.ModTime(), fi.ModTime()); err != nil { - appctx.GetLogger(ctx).Error(). - Err(err). - Str("leafPath", leafPath). - Str("currentPath", currentPath). - Msg("could not propagate change") - return err - } - } - fi, err = os.Stat(currentPath) - if err != nil { - return err - } - etag := calcEtag(ctx, fi) - if err := fs.filecache.SetEtag(ctx, storageID, fs.toDatabasePath(currentPath), etag); err != nil { - appctx.GetLogger(ctx).Error(). - Err(err). - Str("leafPath", leafPath). - Str("currentPath", currentPath). - Msg("could not set etag") - return err - } - - currentPath = filepath.Dir(currentPath) - } - return nil -} - -func (fs *owncloudsqlfs) HashFile(path string) (string, string, string, error) { - sha1h := sha1.New() - md5h := md5.New() - adler32h := adler32.New() - { - f, err := os.Open(path) - if err != nil { - return "", "", "", errors.Wrap(err, "owncloudsql: could not copy bytes for checksumming") - } - defer f.Close() - - r1 := io.TeeReader(f, sha1h) - r2 := io.TeeReader(r1, md5h) - - if _, err := io.Copy(adler32h, r2); err != nil { - return "", "", "", errors.Wrap(err, "owncloudsql: could not copy bytes for checksumming") - } - - return string(sha1h.Sum(nil)), string(md5h.Sum(nil)), string(adler32h.Sum(nil)), nil - } -} - -func readChecksumIntoResourceChecksum(ctx context.Context, checksums, algo string, ri *provider.ResourceInfo) { - re := regexp.MustCompile(strings.ToUpper(algo) + `:(.*)`) - matches := re.FindStringSubmatch(checksums) - if len(matches) < 2 { - appctx.GetLogger(ctx). - Debug(). - Str("nodepath", checksums). - Str("algorithm", algo). - Msg("checksum not set") - } else { - ri.Checksum = &provider.ResourceChecksum{ - Type: storageprovider.PKG2GRPCXS(algo), - Sum: matches[1], - } - } -} - -func readChecksumIntoOpaque(ctx context.Context, checksums, algo string, ri *provider.ResourceInfo) { - re := regexp.MustCompile(strings.ToUpper(algo) + `:(.*)`) - matches := re.FindStringSubmatch(checksums) - if len(matches) < 2 { - appctx.GetLogger(ctx). - Debug(). - Str("nodepath", checksums). - Str("algorithm", algo). - Msg("checksum not set") - } else { - if ri.Opaque == nil { - ri.Opaque = &types.Opaque{ - Map: map[string]*types.OpaqueEntry{}, - } - } - ri.Opaque.Map[algo] = &types.OpaqueEntry{ - Decoder: "plain", - Value: []byte(matches[1]), - } - } -} - -func getResourceType(isDir bool) provider.ResourceType { - if isDir { - return provider.ResourceType_RESOURCE_TYPE_CONTAINER - } - return provider.ResourceType_RESOURCE_TYPE_FILE -} - -// TODO propagate etag and mtime or append event to history? propagate on disk ... -// - but propagation is a separate task. only if upload was successful ... diff --git a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/owncloudsql_unix.go b/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/owncloudsql_unix.go deleted file mode 100644 index 06750bd6440..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/owncloudsql_unix.go +++ /dev/null @@ -1,87 +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. - -//go:build !windows -// +build !windows - -package owncloudsql - -import ( - "context" - "crypto/md5" - "encoding/binary" - "fmt" - "os" - "strings" - "syscall" - - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/owncloud/reva/v2/pkg/appctx" -) - -// TODO(jfd) get rid of the differences between unix and windows. the inode and dev should never be used for the etag because it interferes with backups - -// calcEtag will create an etag based on the md5 of -// - mtime, -// - inode (if available), -// - device (if available) and -// - size. -// errors are logged, but an etag will still be returned -func calcEtag(ctx context.Context, fi os.FileInfo) string { - log := appctx.GetLogger(ctx) - h := md5.New() - err := binary.Write(h, binary.BigEndian, fi.ModTime().UnixNano()) - if err != nil { - log.Error().Err(err).Msg("error writing mtime") - } - stat, ok := fi.Sys().(*syscall.Stat_t) - if ok { - // take device and inode into account - err = binary.Write(h, binary.BigEndian, stat.Ino) - if err != nil { - log.Error().Err(err).Msg("error writing inode") - } - err = binary.Write(h, binary.BigEndian, stat.Dev) - if err != nil { - log.Error().Err(err).Msg("error writing device") - } - } - err = binary.Write(h, binary.BigEndian, fi.Size()) - if err != nil { - log.Error().Err(err).Msg("error writing size") - } - etag := fmt.Sprintf("%x", h.Sum(nil)) - return strings.Trim(etag, "\"") -} - -func (fs *owncloudsqlfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) { - // TODO quota of which storage space? - // we could use the logged in user, but when a user has access to multiple storages this falls short - // for now return quota of root - stat := syscall.Statfs_t{} - err := syscall.Statfs(fs.toInternalPath(ctx, "/"), &stat) - if err != nil { - return 0, 0, 0, err - } - // Total data blocks in filesystem - total := stat.Blocks * uint64(stat.Bsize) - // Free blocks available to unprivileged user - used := (stat.Blocks - uint64(stat.Bavail)) * uint64(stat.Bsize) //nolint:unconvert - remaining := total - used - return total, used, remaining, nil -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/owncloudsql_windows.go b/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/owncloudsql_windows.go deleted file mode 100644 index e9a8fb31251..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/owncloudsql_windows.go +++ /dev/null @@ -1,76 +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. - -//go:build windows -// +build windows - -package owncloudsql - -import ( - "context" - "crypto/md5" - "encoding/binary" - "fmt" - "os" - "strings" - - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/owncloud/reva/v2/pkg/appctx" - "golang.org/x/sys/windows" -) - -// calcEtag will create an etag based on the md5 of -// - mtime, -// - inode (if available), -// - device (if available) and -// - size. -// errors are logged, but an etag will still be returned -func calcEtag(ctx context.Context, fi os.FileInfo) string { - log := appctx.GetLogger(ctx) - h := md5.New() - err := binary.Write(h, binary.BigEndian, fi.ModTime().UnixNano()) - if err != nil { - log.Error().Err(err).Msg("error writing mtime") - } - // device and inode have no meaning on windows - err = binary.Write(h, binary.BigEndian, fi.Size()) - if err != nil { - log.Error().Err(err).Msg("error writing size") - } - etag := fmt.Sprintf(`"%x"`, h.Sum(nil)) - return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\"")) -} - -func (fs *owncloudsqlfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) { - // TODO quota of which storage space? - // we could use the logged in user, but when a user has access to multiple storages this falls short - // for now return quota of root - var free, total, avail uint64 - - pathPtr, err := windows.UTF16PtrFromString(fs.toInternalPath(ctx, "/")) - if err != nil { - return 0, 0, 0, err - } - err = windows.GetDiskFreeSpaceEx(pathPtr, &avail, &total, &free) - if err != nil { - return 0, 0, 0, err - } - - used := total - free - return total, used, free, nil -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/spaces.go b/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/spaces.go deleted file mode 100644 index 389359ebc2a..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/spaces.go +++ /dev/null @@ -1,193 +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 owncloudsql - -import ( - "context" - "fmt" - "strconv" - "strings" - - 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" - ctxpkg "github.com/owncloud/reva/v2/pkg/ctx" - "github.com/owncloud/reva/v2/pkg/errtypes" - "github.com/owncloud/reva/v2/pkg/storage" - "github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/filecache" - "github.com/owncloud/reva/v2/pkg/storagespace" - "github.com/owncloud/reva/v2/pkg/utils" -) - -// ListStorageSpaces lists storage spaces according to the provided filters -func (fs *owncloudsqlfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) { - var ( - spaceID = "*" - // uid *userpb.UserId - ) - - filteringUnsupportedSpaceTypes := false - - for i := range filter { - switch filter[i].Type { - case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE: - t := filter[i].GetSpaceType() - filteringUnsupportedSpaceTypes = (t != "personal" && !strings.HasPrefix(t, "+")) - case provider.ListStorageSpacesRequest_Filter_TYPE_ID: - _, spaceID, _, _ = storagespace.SplitID(filter[i].GetId().OpaqueId) - case provider.ListStorageSpacesRequest_Filter_TYPE_USER: - // uid = filter[i].GetUser() - } - } - if filteringUnsupportedSpaceTypes { - // owncloudsql only supports personal spaces, no need to search for something else - return []*provider.StorageSpace{}, nil - } - - spaces := []*provider.StorageSpace{} - if spaceID == "*" { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - return nil, errtypes.UserRequired("error getting user from context") - } - // if uid != nil && utils.UserIDEqual(uid, u.Id) { - // return nil, errtypes.PermissionDenied("cannot access space of other user?") - // } - space, err := fs.getPersonalSpace(ctx, u) - if err != nil { - return nil, err - } - spaces = append(spaces, space) - } else { - id, err := strconv.Atoi(spaceID) - if err != nil { - // non-numeric space id -> this request is not for us - return []*provider.StorageSpace{}, nil - } - space, err := fs.getSpaceByNumericID(ctx, id) - if err != nil { - return nil, err - } - spaces = append(spaces, space) - } - return spaces, nil -} - -// CreateStorageSpace creates a storage space -func (fs *owncloudsqlfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { - return nil, errtypes.NotSupported("unimplemented: CreateStorageSpace") -} - -// UpdateStorageSpace updates a storage space -func (fs *owncloudsqlfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { - return nil, errtypes.NotSupported("update storage space") -} - -// DeleteStorageSpace deletes a storage space -func (fs *owncloudsqlfs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) (*storage.DeleteStorageSpaceResult, error) { - return nil, errtypes.NotSupported("delete storage space") -} - -// Note: currently unused but will be used later -// func (fs *owncloudsqlfs) listAllPersonalSpaces(ctx context.Context) ([]*provider.StorageSpace, error) { -// storages, err := fs.filecache.ListStorages(true) -// if err != nil { -// return nil, err -// } -// spaces := []*provider.StorageSpace{} -// for _, storage := range storages { -// space, err := fs.storageToSpace(ctx, storage) -// if err != nil { -// return nil, err -// } -// spaces = append(spaces, space) -// } -// return spaces, nil -// } - -func (fs *owncloudsqlfs) getPersonalSpace(ctx context.Context, owner *userpb.User) (*provider.StorageSpace, error) { - storageID, err := fs.filecache.GetNumericStorageID(ctx, "home::"+owner.Username) - if err != nil { - return nil, err - } - storage, err := fs.filecache.GetStorage(ctx, storageID) - if err != nil { - return nil, err - } - root, err := fs.filecache.Get(ctx, storage.NumericID, "") - if err != nil { - return nil, err - } - - space := &provider.StorageSpace{ - Id: &provider.StorageSpaceId{OpaqueId: strconv.Itoa(storage.NumericID)}, - Root: &provider.ResourceId{ - // return ownclouds numeric storage id as the space id! - SpaceId: strconv.Itoa(storage.NumericID), - OpaqueId: strconv.Itoa(root.ID), - }, - Name: owner.Username, - SpaceType: "personal", - Mtime: &types.Timestamp{Seconds: uint64(root.MTime)}, - Owner: owner, - } - space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "spaceAlias", "personal/"+owner.Username) - space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "etag", fmt.Sprintf(`"%s"`, root.Etag)) - return space, nil -} - -func (fs *owncloudsqlfs) getSpaceByNumericID(ctx context.Context, spaceID int) (*provider.StorageSpace, error) { - storage, err := fs.filecache.GetStorage(ctx, spaceID) - if err != nil { - return nil, err - } - if !strings.HasPrefix(storage.ID, "home::") { - return nil, fmt.Errorf("only personal spaces are supported") - } - - return fs.storageToSpace(ctx, storage) -} - -func (fs *owncloudsqlfs) storageToSpace(ctx context.Context, storage *filecache.Storage) (*provider.StorageSpace, error) { - root, err := fs.filecache.Get(ctx, storage.NumericID, "") - if err != nil { - return nil, err - } - ownerName := strings.TrimPrefix(storage.ID, "home::") - owner, err := fs.getUser(ctx, ownerName) - if err != nil { - return nil, err - } - - space := &provider.StorageSpace{ - Id: &provider.StorageSpaceId{OpaqueId: strconv.Itoa(storage.NumericID)}, - Root: &provider.ResourceId{ - // return ownclouds numeric storage id as the space id! - SpaceId: strconv.Itoa(storage.NumericID), - OpaqueId: strconv.Itoa(root.ID), - }, - Name: owner.Username, - SpaceType: "personal", - Mtime: &types.Timestamp{Seconds: uint64(root.MTime)}, - Owner: owner, - } - space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "spaceAlias", "personal/"+owner.Username) - space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "etag", fmt.Sprintf(`"%s"`, root.Etag)) - return space, nil -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/upload.go b/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/upload.go deleted file mode 100644 index 358d35e45c0..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/storage/fs/owncloudsql/upload.go +++ /dev/null @@ -1,43 +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 owncloudsql - -import ( - "context" - - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/owncloud/reva/v2/pkg/errtypes" - "github.com/owncloud/reva/v2/pkg/storage" -) - -func (fs *owncloudsqlfs) MarkProcessing(ctx context.Context, ref *provider.Reference, processing bool, sessionID string) error { - return errtypes.NotSupported("op not supported") -} - -func (fs *owncloudsqlfs) CommitUpload(_ context.Context, _ *provider.Reference, _ string, _ storage.UploadSource) error { - return errtypes.NotSupported("op not supported") -} - -func (fs *owncloudsqlfs) PrepareUpload(_ context.Context, _ *provider.Reference, _ string, info storage.UploadInfo) (*storage.PrepareUploadResult, error) { - return &storage.PrepareUploadResult{VersionCreated: info.NodeExisted}, nil -} - -func (fs *owncloudsqlfs) RollbackUpload(_ context.Context, _ *provider.Reference, _ string, _ storage.RollbackInfo) error { - return nil -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/user/manager/loader/loader.go b/vendor/github.com/owncloud/reva/v2/pkg/user/manager/loader/loader.go index 943c51b4fc4..9afab19d514 100644 --- a/vendor/github.com/owncloud/reva/v2/pkg/user/manager/loader/loader.go +++ b/vendor/github.com/owncloud/reva/v2/pkg/user/manager/loader/loader.go @@ -25,6 +25,5 @@ import ( _ "github.com/owncloud/reva/v2/pkg/user/manager/ldap" _ "github.com/owncloud/reva/v2/pkg/user/manager/memory" _ "github.com/owncloud/reva/v2/pkg/user/manager/nextcloud" - _ "github.com/owncloud/reva/v2/pkg/user/manager/owncloudsql" // Add your own here ) diff --git a/vendor/github.com/owncloud/reva/v2/pkg/user/manager/owncloudsql/accounts/accounts.go b/vendor/github.com/owncloud/reva/v2/pkg/user/manager/owncloudsql/accounts/accounts.go deleted file mode 100644 index 3848bfb6848..00000000000 --- a/vendor/github.com/owncloud/reva/v2/pkg/user/manager/owncloudsql/accounts/accounts.go +++ /dev/null @@ -1,228 +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 accounts - -import ( - "context" - "database/sql" - "strings" - "time" - - "github.com/owncloud/reva/v2/pkg/appctx" - "github.com/pkg/errors" -) - -// Accounts represents oc10-style Accounts -type Accounts struct { - driver string - db *sql.DB - joinUsername, joinUUID, enableMedialSearch bool - selectSQL string -} - -// NewMysql returns a new Cache instance connecting to a MySQL database -func NewMysql(dsn string, joinUsername, joinUUID, enableMedialSearch bool) (*Accounts, error) { - sqldb, err := sql.Open("mysql", dsn) - if err != nil { - return nil, errors.Wrap(err, "error connecting to the database") - } - - // FIXME make configurable - sqldb.SetConnMaxLifetime(time.Minute * 3) - sqldb.SetConnMaxIdleTime(time.Second * 30) - sqldb.SetMaxOpenConns(100) - sqldb.SetMaxIdleConns(10) - - err = sqldb.Ping() - if err != nil { - return nil, errors.Wrap(err, "error connecting to the database") - } - - return New("mysql", sqldb, joinUsername, joinUUID, enableMedialSearch) -} - -// New returns a new Cache instance connecting to the given sql.DB -func New(driver string, sqldb *sql.DB, joinUsername, joinUUID, enableMedialSearch bool) (*Accounts, error) { - - sel := "SELECT id, email, user_id, display_name, quota, last_login, backend, home, state" - from := ` - FROM oc_accounts a - ` - if joinUsername { - sel += ", p.configvalue AS username" - from += `LEFT JOIN oc_preferences p - ON a.user_id=p.userid - AND p.appid='core' - AND p.configkey='username'` - } else { - // fallback to user_id as username - sel += ", user_id AS username" - } - if joinUUID { - sel += ", p2.configvalue AS ownclouduuid" - from += `LEFT JOIN oc_preferences p2 - ON a.user_id=p2.userid - AND p2.appid='core' - AND p2.configkey='ownclouduuid'` - } else { - // fallback to user_id as ownclouduuid - sel += ", user_id AS ownclouduuid" - } - - return &Accounts{ - driver: driver, - db: sqldb, - joinUsername: joinUsername, - joinUUID: joinUUID, - enableMedialSearch: enableMedialSearch, - selectSQL: sel + from, - }, nil -} - -// Account stores information about accounts. -type Account struct { - ID uint64 - Email sql.NullString - UserID string - DisplayName sql.NullString - Quota sql.NullString - LastLogin int - Backend string - Home string - State int8 - Username sql.NullString // optional comes from the oc_preferences - OwnCloudUUID sql.NullString // optional comes from the oc_preferences -} - -func (as *Accounts) rowToAccount(ctx context.Context, row Scannable) (*Account, error) { - a := Account{} - if err := row.Scan(&a.ID, &a.Email, &a.UserID, &a.DisplayName, &a.Quota, &a.LastLogin, &a.Backend, &a.Home, &a.State, &a.Username, &a.OwnCloudUUID); err != nil { - appctx.GetLogger(ctx).Error().Err(err).Msg("could not scan row, skipping") - return nil, err - } - - return &a, nil -} - -// Scannable describes the interface providing a Scan method -type Scannable interface { - Scan(...interface{}) error -} - -// GetAccountByClaim fetches an account by mail, username or userid -func (as *Accounts) GetAccountByClaim(ctx context.Context, claim, value string) (*Account, error) { - // TODO align supported claims with rest driver and the others, maybe refactor into common mapping - var row *sql.Row - var where string - switch claim { - case "mail": - where = "WHERE a.email=?" - // case "uid": - // claim = m.c.Schema.UIDNumber - // case "gid": - // claim = m.c.Schema.GIDNumber - case "username": - if as.joinUsername { - where = "WHERE p.configvalue=?" - } else { - // use user_id as username - where = "WHERE a.user_id=?" - } - case "userid": - if as.joinUUID { - where = "WHERE p2.configvalue=?" - } else { - // use user_id as uuid - where = "WHERE a.user_id=?" - } - default: - return nil, errors.New("owncloudsql: invalid field " + claim) - } - - row = as.db.QueryRowContext(ctx, as.selectSQL+where, value) - - return as.rowToAccount(ctx, row) -} - -func sanitizeWildcards(q string) string { - return strings.ReplaceAll(strings.ReplaceAll(q, "%", `\%`), "_", `\_`) -} - -// FindAccounts searches userid, displayname and email using the given query. The Wildcard caracters % and _ are escaped. -func (as *Accounts) FindAccounts(ctx context.Context, query string) ([]Account, error) { - if as.enableMedialSearch { - query = "%" + sanitizeWildcards(query) + "%" - } - // TODO join oc_account_terms - where := "WHERE a.user_id LIKE ? OR a.display_name LIKE ? OR a.email LIKE ?" - args := []interface{}{query, query, query} - - if as.joinUsername { - where += " OR p.configvalue LIKE ?" - args = append(args, query) - } - if as.joinUUID { - where += " OR p2.configvalue LIKE ?" - args = append(args, query) - } - - rows, err := as.db.QueryContext(ctx, as.selectSQL+where, args...) - if err != nil { - return nil, err - } - defer rows.Close() - - accounts := []Account{} - for rows.Next() { - a := Account{} - if err := rows.Scan(&a.ID, &a.Email, &a.UserID, &a.DisplayName, &a.Quota, &a.LastLogin, &a.Backend, &a.Home, &a.State, &a.Username, &a.OwnCloudUUID); err != nil { - appctx.GetLogger(ctx).Error().Err(err).Msg("could not scan row, skipping") - continue - } - accounts = append(accounts, a) - } - if err = rows.Err(); err != nil { - return nil, err - } - - return accounts, nil -} - -// GetAccountGroups lasts the groups for an account -func (as *Accounts) GetAccountGroups(ctx context.Context, uid string) ([]string, error) { - rows, err := as.db.QueryContext(ctx, "SELECT gid FROM oc_group_user WHERE uid=?", uid) - if err != nil { - return nil, err - } - defer rows.Close() - - groups := []string{} - for rows.Next() { - var group string - if err := rows.Scan(&group); err != nil { - appctx.GetLogger(ctx).Error().Err(err).Msg("could not scan row, skipping") - continue - } - groups = append(groups, group) - } - if err = rows.Err(); err != nil { - return nil, err - } - return groups, nil -} diff --git a/vendor/github.com/owncloud/reva/v2/pkg/user/manager/owncloudsql/accounts/test.sqlite b/vendor/github.com/owncloud/reva/v2/pkg/user/manager/owncloudsql/accounts/test.sqlite deleted file mode 100644 index c68bb753774fc28eda95f71768f5d41d29830363..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90112 zcmeI*T~8a?8Nl%wFc_S`u)86nYK1ZisfZN_HUtP+Z8t!i4O+rW78;0FGJ`!hPHc}e zV=pi54QZwQ2z$|s_F~n3fJ&8C>X&G*Dpl%brD|2FdzIUsIm6gvjDc!ZwOIcVG4pcH z%yWKo=FFTiGx>aNDPOUSUE3*|6=PBvP*hcUXc&s3OpAZl#lK+qjTngp6XLfT8gDf^ zt=vsK=-jK5vNqxD8 zBGxQIh`ldJ;_A^za|mX(m@lRMO#ZmYB4k5zJ~$(eQ?s9lMW(3-)B9dj|7`3;FvnFR zU9p^^D?e)>4U67CHI{}Xq1?4V8WMP+c-s*$9}YzI=OUI*!Y~bqgG!qY?V^SR?_D6G zAVaW@)^XKSis)ARqx#U0`qr(OI|WPZ6tBLx!Efz&!|QYNODSVJn7wV>jv0pB@y3^? zlX+=6x2NyLjpdbfV|im~$#|4{Jh!p5Zj6k?Mn%Ho+Ugw-+YK3lbY3QzvTVBUp6z7o z$tI?zTCRBY+2Yf=XIsW+sjZ+M#^U2zx4tl>Dw5lMQxMk^X|r0f<+RsC$P%WL!Pvmt zTUVocJg)w-GcYJS)~@APrHtiCgr@qMJVFZt6#*!Yh=y{uf-%eGaMH<%mPOsp*rnZk z?zMGru{Hb2zcdR~L0+&+xr()4sqL4*`vjmL`RKME)@W#(@mTz$zHWUbeuis^Lh6R5 z`wwGL{l*RTXdxh$$G)fQk6dYj_2(a(=E6#6)U?L(c)3L2$t&G@>c$y?`>Izh~GqWAQ;<+A@U_6r|Q&4nEG+u&Gywzpe_yuH&{WZAOT$}Fd%Eeb6c{trt1QS_XeDfenxh8HTfobLI>&pM;}@UZ&T$3eM| z+efc*2BVRt5(i6;i|f_Te5qpPEXSxfgW)|jx3RvmxGdK5G_|}g+dBVZqSj8xhOVhW zl&9}-?Qdug<$4PCo;bx{AYsEr!#Uf1Q?)B*y{Yk9rl!~mrdvrD>|DN7FMLx=myj{; zn3>mBsYRKWB6L2H3B(zyBHFS*`ScBKEHAEYq`Z@XciAJ4e{lk;&26L- zZNpSox4t-h#sL%T=k!FVI&@D(E}G3d%>?V{5*N@t-bM6kN4I|Gj=Cjl-0>;Owcb?a z8OJ|k1(Tf(HSa{)+uj*J zFd%>c0tg_000IagfB*srAb>!d2=uF+!@c!CKXCp2|2K;EO`8;uVjzG30tg_000Iag zfB*srAaF?qUZ_`;ab4Gc6d9Z8l@HN8wD!$nxnSM3ot&Ie|41eh#;5b!M<1`OEPSyc z9?JOQktrT~FkNfBSgMR?#l-k-zF@iIGEYpHsMi(Ss`v2{c|9_kAT#AJN0tg_000IagfB*srAb`NJfdBr#?-lKPNDx2(0R#|0 z009ILKmY**5I~@91p3rYU9Z0`(0~4){(sx-UkZW%0tg_000IagfB*srAb@};&>zu{ zU;QU<0{Gwm^ZtJ-2LcEnfB*srAb#A$@}K)y_w16J+Z-|@J1WPhdR3S_@E|NzMFT%Caa~r z{GsV&-Iw-WVpwequ#zuYhjz(IRUNx*jn7+7Az$*o|Bq;2E85rEKeRt-zi-$869^!H z00IagfB*srAbGt{eM+^C!YUrorWa{Ab