diff --git a/.drone.star b/.drone.star index 06df1cb92..f6dda6c54 100644 --- a/.drone.star +++ b/.drone.star @@ -453,6 +453,7 @@ def localApiTests(ctx, storage, suite, accounts_hash_difficulty = 4): "BEHAT_SUITE": suite, "BEHAT_FILTER_TAGS": "~@skip&&~@skipOnOcis-%s-Storage" % ("OC" if storage == "owncloud" else "OCIS"), "PATH_TO_CORE": "/srv/app/testrunner", + "EXPECTED_FAILURES_FILE": "/drone/src/tests/acceptance/expected-failures-localAPI-on-%s-storage.md" % (storage.upper()), "UPLOAD_DELETE_WAIT_TIME": "1" if storage == "owncloud" else 0, }, "commands": [ @@ -1466,6 +1467,7 @@ def ocisServer(storage, accounts_hash_difficulty = 4, volumes = []): "IDP_IDENTIFIER_REGISTRATION_CONF": "/drone/src/tests/config/drone/identifier-registration.yml", "OCIS_LOG_LEVEL": "error", "SETTINGS_DATA_PATH": "/srv/app/tmp/ocis/settings", + "OCIS_INSECURE": "true", } # Pass in "default" accounts_hash_difficulty to not set this environment variable. diff --git a/.vscode/launch.json b/.vscode/launch.json index 106289065..1df7882e2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,12 +7,18 @@ "request": "launch", "mode": "debug", "program": "${workspaceFolder}/ocis/cmd/ocis", - "args": ["server"], + "args": [ + "server" + ], "env": { + // log settings for human developers "OCIS_LOG_LEVEL": "debug", "OCIS_LOG_PRETTY": "true", "OCIS_LOG_COLOR": "true", - "PROXY_ENABLE_BASIC_AUTH": "true" + // enable basic auth for dev setup so that we can use curl for testing + "PROXY_ENABLE_BASIC_AUTH": "true", + // set insecure options because we don't have valid certificates in dev environments + "OCIS_INSECURE": "true", } } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 0db025ace..1421fdd48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The following sections list the changes for unreleased. * Bugfix - Fix opening images in media viewer for some usernames: [#2738](https://github.com/owncloud/ocis/pull/2738) * Bugfix - Fix error logging when there is no thumbnail for a file: [#2702](https://github.com/owncloud/ocis/pull/2702) * Bugfix - Don't announce resharing via capabilities: [#2690](https://github.com/owncloud/ocis/pull/2690) +* Change - Make all insecure options configurable and change the default to false: [#2700](https://github.com/owncloud/ocis/issues/2700) * Enhancement - Add API to list all spaces: [#2692](https://github.com/owncloud/ocis/pull/2692) * Enhancement - Update reva to v1.16: [#2737](https://github.com/owncloud/ocis/pull/2737) @@ -62,6 +63,28 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/2690 +* Change - Make all insecure options configurable and change the default to false: [#2700](https://github.com/owncloud/ocis/issues/2700) + + We had several hard-coded 'insecure' flags. These options are now configurable and default to + false. Also we changed all other 'insecure' flags with a previous default of true to false. + + In development environments using self signed certs (the default) you now need to set these + flags: + + ``` PROXY_OIDC_INSECURE=true STORAGE_FRONTEND_APPPROVIDER_INSECURE=true + STORAGE_FRONTEND_ARCHIVER_INSECURE=true STORAGE_FRONTEND_OCDAV_INSECURE=true + STORAGE_HOME_DATAPROVIDER_INSECURE=true + STORAGE_METADATA_DATAPROVIDER_INSECURE=true STORAGE_OIDC_INSECURE=true + STORAGE_USERS_DATAPROVIDER_INSECURE=true THUMBNAILS_CS3SOURCE_INSECURE=true + THUMBNAILS_WEBDAVSOURCE_INSECURE=true ``` + + As an alternative you also can set a single flag, which configures all options together: + + ``` OCIS_INSECURE=true ``` + + https://github.com/owncloud/ocis/issues/2700 + https://github.com/owncloud/ocis/pull/2745 + * Enhancement - Add API to list all spaces: [#2692](https://github.com/owncloud/ocis/pull/2692) Added a graph endpoint to enable users with the `list-all-spaces` permission to list all diff --git a/Makefile b/Makefile index 258d2f66c..491210e3a 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ OCIS_MODULES = \ # bin file definitions PHP_CS_FIXER=php -d zend.enable_gc=0 vendor-bin/owncloud-codestyle/vendor/bin/php-cs-fixer PHP_CODESNIFFER=vendor-bin/php_codesniffer/vendor/bin/phpcs +PHP_CODEBEAUTIFIER=vendor-bin/php_codesniffer/vendor/bin/phpcbf PHAN=php -d zend.enable_gc=0 vendor-bin/phan/vendor/bin/phan PHPSTAN=php -d zend.enable_gc=0 vendor-bin/phpstan/vendor/bin/phpstan @@ -213,7 +214,7 @@ test-php-style: vendor-bin/owncloud-codestyle/vendor vendor-bin/php_codesniffer/ .PHONY: test-php-style-fix test-php-style-fix: vendor-bin/owncloud-codestyle/vendor $(PHP_CS_FIXER) fix -v --diff --allow-risky yes - + $(PHP_CODEBEAUTIFIER) --cache --runtime-set ignore_warnings_on_exit --standard=phpcs.xml tests/acceptance vendor-bin/owncloud-codestyle/vendor: vendor/bamarni/composer-bin-plugin vendor-bin/owncloud-codestyle/composer.lock composer bin owncloud-codestyle install --no-progress diff --git a/changelog/unreleased/insecure-options.md b/changelog/unreleased/insecure-options.md new file mode 100644 index 000000000..ae56ea1ea --- /dev/null +++ b/changelog/unreleased/insecure-options.md @@ -0,0 +1,27 @@ +Change: Make all insecure options configurable and change the default to false + +We had several hard-coded 'insecure' flags. These options are now configurable and default to false. Also we changed all other 'insecure' flags with a previous default of true to false. + +In development environments using self signed certs (the default) you now need to set these flags: + +``` +PROXY_OIDC_INSECURE=true +STORAGE_FRONTEND_APPPROVIDER_INSECURE=true +STORAGE_FRONTEND_ARCHIVER_INSECURE=true +STORAGE_FRONTEND_OCDAV_INSECURE=true +STORAGE_HOME_DATAPROVIDER_INSECURE=true +STORAGE_METADATA_DATAPROVIDER_INSECURE=true +STORAGE_OIDC_INSECURE=true +STORAGE_USERS_DATAPROVIDER_INSECURE=true +THUMBNAILS_CS3SOURCE_INSECURE=true +THUMBNAILS_WEBDAVSOURCE_INSECURE=true +``` + +As an alternative you also can set a single flag, which configures all options together: + +``` +OCIS_INSECURE=true +``` + +https://github.com/owncloud/ocis/issues/2700 +https://github.com/owncloud/ocis/pull/2745 diff --git a/deployments/examples/cs3_users_ocis/docker-compose.yml b/deployments/examples/cs3_users_ocis/docker-compose.yml index 9b9f10f77..4386238ec 100644 --- a/deployments/examples/cs3_users_ocis/docker-compose.yml +++ b/deployments/examples/cs3_users_ocis/docker-compose.yml @@ -79,12 +79,13 @@ services: OCIS_URL: https://${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_DOMAIN: ${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose - PROXY_OIDC_INSECURE: "${INSECURE:-false}" # needed if Traefik is using self generated certificates PROXY_TLS: "false" # do not use SSL between Traefik and oCIS # change default secrets OCIS_JWT_SECRET: ${OCIS_JWT_SECRET:-Pive-Fumkiu4} STORAGE_TRANSFER_SECRET: ${STORAGE_TRANSFER_SECRET:-replace-me-with-a-transfer-secret} OCIS_MACHINE_AUTH_API_KEY: ${OCIS_MACHINE_AUTH_API_KEY:-change-me-please} + # INSECURE: needed if oCIS / Traefik is using self generated certificates + OCIS_INSECURE: "${INSECURE:-false}" volumes: - ./config/ocis/entrypoint-override.sh:/entrypoint-override.sh - ./config/ocis/web-config.dist.json:/config/web-config.dist.json diff --git a/deployments/examples/oc10_ocis_parallel/docker-compose.yml b/deployments/examples/oc10_ocis_parallel/docker-compose.yml index c49892791..3469715c0 100644 --- a/deployments/examples/oc10_ocis_parallel/docker-compose.yml +++ b/deployments/examples/oc10_ocis_parallel/docker-compose.yml @@ -109,13 +109,14 @@ services: # General oCIS config OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose OCIS_URL: https://${CLOUD_DOMAIN:-cloud.owncloud.test} - PROXY_OIDC_INSECURE: "${INSECURE:-false}" # needed if Traefik is using self generated certificates PROXY_TLS: "false" # do not use SSL between Traefik and oCIS PROXY_CONFIG_FILE: "/var/tmp/ocis/.config/proxy-config.json" # change default secrets OCIS_JWT_SECRET: ${OCIS_JWT_SECRET:-Pive-Fumkiu4} STORAGE_TRANSFER_SECRET: ${STORAGE_TRANSFER_SECRET:-replace-me-with-a-transfer-secret} OCIS_MACHINE_AUTH_API_KEY: ${OCIS_MACHINE_AUTH_API_KEY:-change-me-please} + # INSECURE: needed if oCIS / Traefik is using self generated certificates + OCIS_INSECURE: "${INSECURE:-false}" volumes: - ./config/ocis/entrypoint-override.sh:/entrypoint-override.sh - ./config/ocis/proxy.json:/etc/ocis/proxy.json diff --git a/deployments/examples/ocis_hello/docker-compose.yml b/deployments/examples/ocis_hello/docker-compose.yml index 997a49141..19aac6991 100644 --- a/deployments/examples/ocis_hello/docker-compose.yml +++ b/deployments/examples/ocis_hello/docker-compose.yml @@ -53,7 +53,6 @@ services: OCIS_URL: https://${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_DOMAIN: ${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose - PROXY_OIDC_INSECURE: "${INSECURE:-false}" # needed if Traefik is using self generated certificates PROXY_TLS: "false" # do not use SSL between Traefik and oCIS # change default secrets IDP_LDAP_BIND_PASSWORD: ${IDP_LDAP_BIND_PASSWORD:-idp} @@ -65,6 +64,8 @@ services: WEB_UI_CONFIG: "/var/tmp/ocis/.config/web-config.json" # make settings service available to oCIS Hello SETTINGS_GRPC_ADDR: 0.0.0.0:9191 + # INSECURE: needed if oCIS / Traefik is using self generated certificates + OCIS_INSECURE: "${INSECURE:-false}" volumes: - ./config/ocis/entrypoint-override.sh:/entrypoint-override.sh - ./config/ocis/web-config.dist.json:/config/web-config.dist.json diff --git a/deployments/examples/ocis_keycloak/docker-compose.yml b/deployments/examples/ocis_keycloak/docker-compose.yml index 00301fa35..dd2be4da7 100644 --- a/deployments/examples/ocis_keycloak/docker-compose.yml +++ b/deployments/examples/ocis_keycloak/docker-compose.yml @@ -62,7 +62,6 @@ services: # general config OCIS_URL: https://${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose - PROXY_OIDC_INSECURE: "${INSECURE:-false}" # needed if Traefik is using self generated certificates PROXY_TLS: "false" # do not use SSL between Traefik and oCIS ACCOUNTS_DEMO_USERS_AND_GROUPS: false # don't generate demo users # change default secrets @@ -71,6 +70,8 @@ services: OCIS_JWT_SECRET: ${OCIS_JWT_SECRET:-Pive-Fumkiu4} STORAGE_TRANSFER_SECRET: ${STORAGE_TRANSFER_SECRET:-replace-me-with-a-transfer-secret} OCIS_MACHINE_AUTH_API_KEY: ${OCIS_MACHINE_AUTH_API_KEY:-change-me-please} + # INSECURE: needed if oCIS / Traefik is using self generated certificates + OCIS_INSECURE: "${INSECURE:-false}" volumes: - ./config/ocis/entrypoint-override.sh:/entrypoint-override.sh - ocis-data:/var/lib/ocis diff --git a/deployments/examples/ocis_s3/docker-compose.yml b/deployments/examples/ocis_s3/docker-compose.yml index 51b3a7c55..996262072 100644 --- a/deployments/examples/ocis_s3/docker-compose.yml +++ b/deployments/examples/ocis_s3/docker-compose.yml @@ -52,7 +52,6 @@ services: environment: OCIS_URL: https://${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose - PROXY_OIDC_INSECURE: "${INSECURE:-false}" # needed if Traefik is using self generated certificates PROXY_TLS: "false" # do not use SSL between Traefik and oCIS # change default secrets IDP_LDAP_BIND_PASSWORD: ${IDP_LDAP_BIND_PASSWORD:-idp} @@ -70,6 +69,8 @@ services: STORAGE_USERS_DRIVER_S3NG_ACCESS_KEY: ${MINIO_ACCESS_KEY:-ocis} STORAGE_USERS_DRIVER_S3NG_SECRET_KEY: ${MINIO_SECRET_KEY:-ocis-secret-key} STORAGE_USERS_DRIVER_S3NG_BUCKET: ${MINIO_BUCKET:-ocis-bucket} + # INSECURE: needed if oCIS / Traefik is using self generated certificates + OCIS_INSECURE: "${INSECURE:-false}" volumes: - ./config/ocis/entrypoint-override.sh:/entrypoint-override.sh - ocis-data:/var/lib/ocis diff --git a/deployments/examples/ocis_traefik/docker-compose.yml b/deployments/examples/ocis_traefik/docker-compose.yml index 029f718b2..53b8ca154 100644 --- a/deployments/examples/ocis_traefik/docker-compose.yml +++ b/deployments/examples/ocis_traefik/docker-compose.yml @@ -52,7 +52,6 @@ services: environment: OCIS_URL: https://${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose - PROXY_OIDC_INSECURE: "${INSECURE:-false}" # needed if Traefik is using self generated certificates PROXY_TLS: "false" # do not use SSL between Traefik and oCIS # change default secrets IDP_LDAP_BIND_PASSWORD: ${IDP_LDAP_BIND_PASSWORD:-idp} @@ -60,6 +59,8 @@ services: OCIS_JWT_SECRET: ${OCIS_JWT_SECRET:-Pive-Fumkiu4} STORAGE_TRANSFER_SECRET: ${STORAGE_TRANSFER_SECRET:-replace-me-with-a-transfer-secret} OCIS_MACHINE_AUTH_API_KEY: ${OCIS_MACHINE_AUTH_API_KEY:-change-me-please} + # INSECURE: needed if oCIS / Traefik is using self generated certificates + OCIS_INSECURE: "${INSECURE:-false}" volumes: - ./config/ocis/entrypoint-override.sh:/entrypoint-override.sh - ocis-data:/var/lib/ocis diff --git a/deployments/examples/ocis_wopi/docker-compose.yml b/deployments/examples/ocis_wopi/docker-compose.yml index d67da4d99..a38ee897e 100644 --- a/deployments/examples/ocis_wopi/docker-compose.yml +++ b/deployments/examples/ocis_wopi/docker-compose.yml @@ -58,7 +58,6 @@ services: OCIS_URL: https://${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_DOMAIN: ${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose - PROXY_OIDC_INSECURE: "${INSECURE:-false}" # needed if Traefik is using self generated certificates PROXY_TLS: "false" # do not use SSL between Traefik and oCIS # change default secrets IDP_LDAP_BIND_PASSWORD: ${IDP_LDAP_BIND_PASSWORD:-idp} @@ -69,6 +68,8 @@ services: # app registry STORAGE_GATEWAY_GRPC_ADDR: 0.0.0.0:9142 # make the REVA gateway accessible to the app drivers STORAGE_APP_REGISTRY_MIMETYPES_JSON: /var/tmp/ocis/app-config/mimetypes.json + # INSECURE: needed if oCIS / Traefik is using self generated certificates + OCIS_INSECURE: "${INSECURE:-false}" volumes: - ./config/ocis/entrypoint-override.sh:/entrypoint-override.sh - ./config/ocis/mimetypes.json:/var/tmp/ocis/app-config/mimetypes.json diff --git a/docs/ocis/deployment/basic-remote-setup.md b/docs/ocis/deployment/basic-remote-setup.md index 96974c153..6569309ab 100644 --- a/docs/ocis/deployment/basic-remote-setup.md +++ b/docs/ocis/deployment/basic-remote-setup.md @@ -29,9 +29,10 @@ For the following examples you need to have the oCIS binary in your current work ### Using automatically generated certificates -In order to run oCIS with automatically generated and self signed certificates please execute following command. You need to replace `your-host` with an IP or hostname. +In order to run oCIS with automatically generated and self signed certificates please execute following command. You need to replace `your-host` with an IP or hostname. Since you have only self signed certificates you need to have `OCIS_INSECURE` set to `true`. ```bash +OCIS_INSECURE=true \ PROXY_HTTP_ADDR=0.0.0.0:9200 \ OCIS_URL=https://your-host:9200 \ ./ocis server @@ -42,6 +43,7 @@ OCIS_URL=https://your-host:9200 \ If you have your own certificates already in place, you may want to make oCIS use them: ```bash +OCIS_INSECURE=false \ PROXY_HTTP_ADDR=0.0.0.0:9200 \ OCIS_URL=https://your-host:9200 \ PROXY_TRANSPORT_TLS_KEY=./certs/your-host.key \ @@ -49,6 +51,8 @@ PROXY_TRANSPORT_TLS_CERT=./certs/your-host.crt \ ./ocis server ``` +If you generated these certificates on your own, you might need to set `OCIS_INSECURE` to `true`. + For more configuration options check the configuration section in [oCIS]({{< ref "../configuration" >}}) and the oCIS extensions. ## Start the oCIS fullstack server with Docker Compose diff --git a/docs/ocis/deployment/systemd.md b/docs/ocis/deployment/systemd.md index 716e690ef..8ac0a1d8f 100644 --- a/docs/ocis/deployment/systemd.md +++ b/docs/ocis/deployment/systemd.md @@ -45,6 +45,7 @@ In order to create the file we need first to create the folder `/etc/ocis/` and ``` OCIS_URL=https://some-hostname-or-ip:9200 PROXY_HTTP_ADDR=0.0.0.0:9200 +OCIS_INSECURE=false OCIS_LOG_LEVEL=error @@ -56,7 +57,7 @@ PROXY_TRANSPORT_TLS_CERT=/etc/ocis/proxy/server.crt PROXY_TRANSPORT_TLS_KEY=/etc/ocis/proxy/server.key ``` -Please change your `OCIS_URL` in order to reflect your actual deployment. +Please change your `OCIS_URL` in order to reflect your actual deployment. If you are using self signed certificates you need to set `OCIS_INSECURE=true` in `/etc/ocis/ocis.env`. ## Starting the oCIS service diff --git a/docs/ocis/development/testing.md b/docs/ocis/development/testing.md index d61d7eff4..60a68c476 100644 --- a/docs/ocis/development/testing.md +++ b/docs/ocis/development/testing.md @@ -98,7 +98,7 @@ git clone https://github.com/owncloud/core.git To start ocis: ``` -PROXY_ENABLE_BASIC_AUTH=true bin/ocis server +OCIS_INSECURE=true PROXY_ENABLE_BASIC_AUTH=true bin/ocis server ``` `PROXY_ENABLE_BASIC_AUTH` will allow the acceptance tests to make requests against the provisioning api (and other endpoints) using basic auth. diff --git a/graph/pkg/cs3/client.go b/graph/pkg/cs3/client.go deleted file mode 100644 index 9eed1a361..000000000 --- a/graph/pkg/cs3/client.go +++ /dev/null @@ -1,26 +0,0 @@ -package cs3 - -import ( - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - - "google.golang.org/grpc" -) - -func newConn(endpoint string) (*grpc.ClientConn, error) { - conn, err := grpc.Dial(endpoint, grpc.WithInsecure()) - if err != nil { - return nil, err - } - - return conn, nil -} - -// GetGatewayServiceClient returns a new cs3 gateway client -func GetGatewayServiceClient(endpoint string) (gateway.GatewayAPIClient, error) { - conn, err := newConn(endpoint) - if err != nil { - return nil, err - } - - return gateway.NewGatewayAPIClient(conn), nil -} diff --git a/graph/pkg/service/v0/graph.go b/graph/pkg/service/v0/graph.go index 97005ce5a..51f5cc604 100644 --- a/graph/pkg/service/v0/graph.go +++ b/graph/pkg/service/v0/graph.go @@ -4,9 +4,9 @@ import ( "net/http" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/go-chi/chi/v5" "github.com/owncloud/ocis/graph/pkg/config" - "github.com/owncloud/ocis/graph/pkg/cs3" "github.com/owncloud/ocis/ocis-pkg/log" ) @@ -24,7 +24,7 @@ func (g Graph) ServeHTTP(w http.ResponseWriter, r *http.Request) { // GetClient returns a gateway client to talk to reva func (g Graph) GetClient() (gateway.GatewayAPIClient, error) { - return cs3.GetGatewayServiceClient(g.config.Reva.Address) + return pool.GetGatewayServiceClient(g.config.Reva.Address) } // The key type is unexported to prevent collisions with context keys defined in diff --git a/ocs/pkg/config/config.go b/ocs/pkg/config/config.go index f2ee415b9..7be7e8622 100644 --- a/ocs/pkg/config/config.go +++ b/ocs/pkg/config/config.go @@ -45,6 +45,11 @@ type Tracing struct { Service string `mapstructure:"service"` } +// Reva defines all available REVA configuration. +type Reva struct { + Address string `address` +} + // TokenManager is the config for using the reva token manager type TokenManager struct { JWTSecret string `mapstructure:"jwt_secret"` @@ -67,7 +72,7 @@ type Config struct { TokenManager TokenManager `mapstructure:"token_manager"` Service Service `mapstructure:"service"` AccountBackend string `mapstructure:"account_backend"` - RevaAddress string `mapstructure:"reva_address"` + Reva Reva `mapstructure:"reva"` StorageUsersDriver string `mapstructure:"storage_users_driver"` MachineAuthAPIKey string `mapstructure:"machine_auth_api_key"` IdentityManagement IdentityManagement `mapstructure:"identity_management"` @@ -116,7 +121,7 @@ func DefaultConfig() *Config { Namespace: "com.owncloud.web", }, AccountBackend: "accounts", - RevaAddress: "127.0.0.1:9142", + Reva: Reva{Address: "127.0.0.1:9142"}, StorageUsersDriver: "ocis", MachineAuthAPIKey: "change-me-please", IdentityManagement: IdentityManagement{ diff --git a/ocs/pkg/service/v0/service.go b/ocs/pkg/service/v0/service.go index a6875cc4b..146561f15 100644 --- a/ocs/pkg/service/v0/service.go +++ b/ocs/pkg/service/v0/service.go @@ -4,6 +4,7 @@ import ( "net/http" "time" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/ocis-pkg/service/grpc" "github.com/go-chi/chi/v5" @@ -19,7 +20,6 @@ import ( ocsm "github.com/owncloud/ocis/ocs/pkg/middleware" "github.com/owncloud/ocis/ocs/pkg/service/v0/data" "github.com/owncloud/ocis/ocs/pkg/service/v0/response" - "github.com/owncloud/ocis/proxy/pkg/cs3" "github.com/owncloud/ocis/proxy/pkg/user/backend" settings "github.com/owncloud/ocis/settings/pkg/proto/v0" ) @@ -161,9 +161,9 @@ func (o Ocs) getAccountService() accounts.AccountsService { } func (o Ocs) getCS3Backend() backend.UserBackend { - revaClient, err := cs3.GetGatewayServiceClient(o.config.RevaAddress) + revaClient, err := pool.GetGatewayServiceClient(o.config.Reva.Address) if err != nil { - o.logger.Fatal().Msgf("could not get reva client at address %s", o.config.RevaAddress) + o.logger.Fatal().Msgf("could not get reva client at address %s", o.config.Reva.Address) } return backend.NewCS3UserBackend(nil, revaClient, o.config.MachineAuthAPIKey, o.logger) } diff --git a/ocs/pkg/service/v0/users.go b/ocs/pkg/service/v0/users.go index 78763f47d..c9108a156 100644 --- a/ocs/pkg/service/v0/users.go +++ b/ocs/pkg/service/v0/users.go @@ -375,7 +375,7 @@ func (o Ocs) DeleteUser(w http.ResponseWriter, r *http.Request) { return } - if o.config.RevaAddress != "" && o.config.StorageUsersDriver != "owncloud" { + if o.config.Reva.Address != "" && o.config.StorageUsersDriver != "owncloud" { t, err := o.mintTokenForUser(r.Context(), account) if err != nil { mustNotFail(render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, errors.Wrap(err, "error minting token").Error()))) @@ -384,7 +384,7 @@ func (o Ocs) DeleteUser(w http.ResponseWriter, r *http.Request) { ctx := metadata.AppendToOutgoingContext(r.Context(), revactx.TokenHeader, t) - gwc, err := pool.GetGatewayServiceClient(o.config.RevaAddress) + gwc, err := pool.GetGatewayServiceClient(o.config.Reva.Address) if err != nil { o.logger.Error().Err(err).Msg("error securing a connection to Reva gateway") } diff --git a/storage/pkg/command/frontend.go b/storage/pkg/command/frontend.go index c2fc65405..19822f7f2 100644 --- a/storage/pkg/command/frontend.go +++ b/storage/pkg/command/frontend.go @@ -171,12 +171,12 @@ func frontendConfigFromStruct(c *cli.Context, cfg *config.Config, filesCfg map[s "prefix": cfg.Reva.Frontend.AppProviderPrefix, "transfer_shared_secret": cfg.Reva.TransferSecret, "timeout": 86400, - "insecure": true, + "insecure": cfg.Reva.Frontend.AppProviderInsecure, }, "archiver": map[string]interface{}{ "prefix": cfg.Reva.Frontend.ArchiverPrefix, "timeout": 86400, - "insecure": true, + "insecure": cfg.Reva.Frontend.ArchiverInsecure, "max_num_files": cfg.Reva.Archiver.MaxNumFiles, "max_size": cfg.Reva.Archiver.MaxSize, }, @@ -191,7 +191,7 @@ func frontendConfigFromStruct(c *cli.Context, cfg *config.Config, filesCfg map[s "files_namespace": cfg.Reva.OCDav.DavFilesNamespace, "webdav_namespace": cfg.Reva.OCDav.WebdavNamespace, "timeout": 86400, - "insecure": true, + "insecure": cfg.Reva.Frontend.OCDavInsecure, "public_url": cfg.Reva.Frontend.PublicURL, }, "ocs": map[string]interface{}{ diff --git a/storage/pkg/command/storagehome.go b/storage/pkg/command/storagehome.go index 4df8524e9..fff984b13 100644 --- a/storage/pkg/command/storagehome.go +++ b/storage/pkg/command/storagehome.go @@ -128,7 +128,7 @@ func storageHomeConfigFromStruct(c *cli.Context, cfg *config.Config) map[string] "driver": cfg.Reva.StorageHome.Driver, "drivers": storagedrivers.HomeDrivers(cfg), "timeout": 86400, - "insecure": true, + "insecure": cfg.Reva.StorageHome.DataProvider.Insecure, "disable_tus": false, }, }, diff --git a/storage/pkg/command/storagemetadata.go b/storage/pkg/command/storagemetadata.go index 74d8b82b4..29cd5deb1 100644 --- a/storage/pkg/command/storagemetadata.go +++ b/storage/pkg/command/storagemetadata.go @@ -150,7 +150,7 @@ func storageMetadataFromStruct(c *cli.Context, cfg *config.Config) map[string]in "driver": cfg.Reva.StorageMetadata.Driver, "drivers": storagedrivers.MetadataDrivers(cfg), "timeout": 86400, - "insecure": true, + "insecure": cfg.Reva.StorageMetadata.DataProvider.Insecure, "disable_tus": true, }, }, diff --git a/storage/pkg/command/storageusers.go b/storage/pkg/command/storageusers.go index 6f01defad..99ae03348 100644 --- a/storage/pkg/command/storageusers.go +++ b/storage/pkg/command/storageusers.go @@ -128,7 +128,7 @@ func storageUsersConfigFromStruct(c *cli.Context, cfg *config.Config) map[string "driver": cfg.Reva.StorageUsers.Driver, "drivers": storagedrivers.UserDrivers(cfg), "timeout": 86400, - "insecure": true, + "insecure": cfg.Reva.StorageUsers.DataProvider.Insecure, "disable_tus": false, }, }, diff --git a/storage/pkg/config/config.go b/storage/pkg/config/config.go index a18c19ce3..4797b7736 100644 --- a/storage/pkg/config/config.go +++ b/storage/pkg/config/config.go @@ -144,10 +144,13 @@ type Groups struct { type FrontendPort struct { Port + AppProviderInsecure bool AppProviderPrefix string + ArchiverInsecure bool ArchiverPrefix string DatagatewayPrefix string Favorites bool + OCDavInsecure bool OCDavPrefix string OCSPrefix string OCSSharePrefix string @@ -175,6 +178,10 @@ type DataGatewayPort struct { PublicURL string } +type DataProvider struct { + Insecure bool +} + // StoragePort defines the available storage configuration. type StoragePort struct { Port @@ -186,9 +193,10 @@ type StoragePort struct { DataServerURL string // for HTTP ports with only one http service - HTTPPrefix string - TempFolder string - ReadOnly bool + HTTPPrefix string + TempFolder string + ReadOnly bool + DataProvider DataProvider } // PublicStorage configures a public storage provider diff --git a/storage/pkg/flagset/authbearer.go b/storage/pkg/flagset/authbearer.go index 73bfeb91e..d41a89558 100644 --- a/storage/pkg/flagset/authbearer.go +++ b/storage/pkg/flagset/authbearer.go @@ -30,9 +30,9 @@ func AuthBearerWithConfig(cfg *config.Config) []cli.Flag { }, &cli.BoolFlag{ Name: "oidc-insecure", - Value: flags.OverrideDefaultBool(cfg.Reva.OIDC.Insecure, true), + Value: flags.OverrideDefaultBool(cfg.Reva.OIDC.Insecure, false), Usage: "OIDC allow insecure communication", - EnvVars: []string{"STORAGE_OIDC_INSECURE"}, + EnvVars: []string{"STORAGE_OIDC_INSECURE", "OCIS_INSECURE"}, Destination: &cfg.Reva.OIDC.Insecure, }, &cli.StringFlag{ diff --git a/storage/pkg/flagset/frontend.go b/storage/pkg/flagset/frontend.go index eb963a7e6..09e3aee1d 100644 --- a/storage/pkg/flagset/frontend.go +++ b/storage/pkg/flagset/frontend.go @@ -119,6 +119,13 @@ func FrontendWithConfig(cfg *config.Config) []cli.Flag { EnvVars: []string{"STORAGE_FRONTEND_APPPROVIDER_PREFIX"}, Destination: &cfg.Reva.Frontend.AppProviderPrefix, }, + &cli.BoolFlag{ + Name: "approvider-insecure", + Value: flags.OverrideDefaultBool(cfg.Reva.Frontend.AppProviderInsecure, false), + Usage: "approvider insecure", + EnvVars: []string{"STORAGE_FRONTEND_APPPROVIDER_INSECURE", "OCIS_INSECURE"}, + Destination: &cfg.Reva.Frontend.AppProviderInsecure, + }, &cli.StringFlag{ Name: "archiver-prefix", Value: flags.OverrideDefaultString(cfg.Reva.Frontend.ArchiverPrefix, "archiver"), @@ -126,6 +133,13 @@ func FrontendWithConfig(cfg *config.Config) []cli.Flag { EnvVars: []string{"STORAGE_FRONTEND_ARCHIVER_PREFIX"}, Destination: &cfg.Reva.Frontend.ArchiverPrefix, }, + &cli.BoolFlag{ + Name: "archiver-insecure", + Value: flags.OverrideDefaultBool(cfg.Reva.Frontend.ArchiverInsecure, false), + Usage: "archiver insecure", + EnvVars: []string{"STORAGE_FRONTEND_ARCHIVER_INSECURE", "OCIS_INSECURE"}, + Destination: &cfg.Reva.Frontend.ArchiverInsecure, + }, &cli.StringFlag{ Name: "datagateway-prefix", Value: flags.OverrideDefaultString(cfg.Reva.Frontend.DatagatewayPrefix, "data"), @@ -147,6 +161,13 @@ func FrontendWithConfig(cfg *config.Config) []cli.Flag { EnvVars: []string{"STORAGE_FRONTEND_OCDAV_PREFIX"}, Destination: &cfg.Reva.Frontend.OCDavPrefix, }, + &cli.BoolFlag{ + Name: "ocdav-insecure", + Value: flags.OverrideDefaultBool(cfg.Reva.Frontend.OCDavInsecure, false), + Usage: "owncloud webdav insecure", + EnvVars: []string{"STORAGE_FRONTEND_OCDAV_INSECURE", "OCIS_INSECURE"}, + Destination: &cfg.Reva.Frontend.OCDavInsecure, + }, &cli.StringFlag{ Name: "ocs-prefix", Value: flags.OverrideDefaultString(cfg.Reva.Frontend.OCSPrefix, "ocs"), diff --git a/storage/pkg/flagset/storagehome.go b/storage/pkg/flagset/storagehome.go index 6df9bf0c5..76eb53d70 100644 --- a/storage/pkg/flagset/storagehome.go +++ b/storage/pkg/flagset/storagehome.go @@ -130,6 +130,13 @@ func StorageHomeWithConfig(cfg *config.Config) []cli.Flag { EnvVars: []string{"STORAGE_HOME_TMP_FOLDER"}, Destination: &cfg.Reva.StorageHome.TempFolder, }, + &cli.BoolFlag{ + Name: "dataprovider-insecure", + Value: flags.OverrideDefaultBool(cfg.Reva.StorageHome.DataProvider.Insecure, false), + Usage: "dataprovider insecure", + EnvVars: []string{"STORAGE_HOME_DATAPROVIDER_INSECURE", "OCIS_INSECURE"}, + Destination: &cfg.Reva.StorageHome.DataProvider.Insecure, + }, // some drivers need to look up users at the gateway diff --git a/storage/pkg/flagset/storagemetadata.go b/storage/pkg/flagset/storagemetadata.go index 6af75d2e3..4b80756f0 100644 --- a/storage/pkg/flagset/storagemetadata.go +++ b/storage/pkg/flagset/storagemetadata.go @@ -69,6 +69,13 @@ func StorageMetadata(cfg *config.Config) []cli.Flag { EnvVars: []string{"STORAGE_METADATA_DRIVER"}, Destination: &cfg.Reva.StorageMetadata.Driver, }, + &cli.BoolFlag{ + Name: "dataprovider-insecure", + Value: flags.OverrideDefaultBool(cfg.Reva.StorageMetadata.DataProvider.Insecure, false), + Usage: "dataprovider insecure", + EnvVars: []string{"STORAGE_METADATA_DATAPROVIDER_INSECURE", "OCIS_INSECURE"}, + Destination: &cfg.Reva.StorageMetadata.DataProvider.Insecure, + }, // some drivers need to look up users at the gateway diff --git a/storage/pkg/flagset/storageusers.go b/storage/pkg/flagset/storageusers.go index b801d2dd8..6be58bc33 100644 --- a/storage/pkg/flagset/storageusers.go +++ b/storage/pkg/flagset/storageusers.go @@ -78,6 +78,13 @@ func StorageUsersWithConfig(cfg *config.Config) []cli.Flag { EnvVars: []string{"STORAGE_USERS_DRIVER"}, Destination: &cfg.Reva.StorageUsers.Driver, }, + &cli.BoolFlag{ + Name: "dataprovider-insecure", + Value: flags.OverrideDefaultBool(cfg.Reva.StorageUsers.DataProvider.Insecure, false), + Usage: "dataprovider insecure", + EnvVars: []string{"STORAGE_USERS_DATAPROVIDER_INSECURE", "OCIS_INSECURE"}, + Destination: &cfg.Reva.StorageUsers.DataProvider.Insecure, + }, &cli.BoolFlag{ Name: "read-only", Value: flags.OverrideDefaultBool(cfg.Reva.StorageUsers.ReadOnly, false), diff --git a/tests/acceptance/docker/src/ocis-base.yml b/tests/acceptance/docker/src/ocis-base.yml index 4154bd0d7..7cd72650c 100644 --- a/tests/acceptance/docker/src/ocis-base.yml +++ b/tests/acceptance/docker/src/ocis-base.yml @@ -14,6 +14,7 @@ services: WEB_UI_CONFIG: /drone/src/tests/config/drone/ocis-config.json IDP_IDENTIFIER_REGISTRATION_CONF: /drone/src/tests/config/drone/identifier-registration.yml ACCOUNTS_HASH_DIFFICULTY: 4 + OCIS_INSECURE: "true" # s3ng specific settings STORAGE_USERS_DRIVER_S3NG_ENDPOINT: http://ceph:8080 STORAGE_USERS_DRIVER_S3NG_REGION: default diff --git a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md new file mode 100644 index 000000000..a8a479882 --- /dev/null +++ b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md @@ -0,0 +1,5 @@ +## Scenarios from OCIS API tests that are expected to fail with OCIS storage + +#### [downloading the /Shares folder using the archiver endpoint does not work](https://github.com/owncloud/ocis/issues/2751) +- [apiArchiver/downloadById.feature:134](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiArchiver/downloadById.feature#L134) +- [apiArchiver/downloadById.feature:135](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiArchiver/downloadById.feature#L135) diff --git a/tests/acceptance/features/apiArchiver/downloadById.feature b/tests/acceptance/features/apiArchiver/downloadById.feature index 75dba8189..518241951 100644 --- a/tests/acceptance/features/apiArchiver/downloadById.feature +++ b/tests/acceptance/features/apiArchiver/downloadById.feature @@ -11,21 +11,125 @@ Feature: download multiple resources bundled into an archive Background: Given user "Alice" has been created with default attributes and without skeleton files - Scenario: download a single file + + Scenario Outline: download a single file Given user "Alice" has uploaded file with content "some data" to "/textfile0.txt" - When user "Alice" downloads the archive of "/textfile0.txt" using the resource id + When user "Alice" downloads the archive of "/textfile0.txt" using the resource id and setting these headers + | header | value | + | User-Agent | | Then the HTTP status code should be "200" - And the downloaded archive should contain these files: + And the downloaded archive should contain these files: | name | content | | textfile0.txt | some data | + Examples: + | user-agent | archive-type | + | Linux | tar | + | Windows NT | zip | - Scenario: download a single folder + + Scenario Outline: download a single folder Given user "Alice" has created folder "my_data" And user "Alice" has uploaded file with content "some data" to "/my_data/textfile0.txt" And user "Alice" has uploaded file with content "more data" to "/my_data/an_other_file.txt" - When user "Alice" downloads the archive of "/my_data" using the resource id + When user "Alice" downloads the archive of "/my_data" using the resource id and setting these headers + | header | value | + | User-Agent | | Then the HTTP status code should be "200" - And the downloaded archive should contain these files: + And the downloaded archive should contain these files: | name | content | | my_data/textfile0.txt | some data | | my_data/an_other_file.txt | more data | + Examples: + | user-agent | archive-type | + | Linux | tar | + | Windows NT | zip | + + + Scenario: download multiple files and folders + Given user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has uploaded file with content "other data" to "/textfile1.txt" + And user "Alice" has created folder "my_data" + And user "Alice" has uploaded file with content "some data" to "/my_data/textfile2.txt" + And user "Alice" has created folder "more_data" + And user "Alice" has uploaded file with content "more data" to "/more_data/an_other_file.txt" + When user "Alice" downloads the archive of these items using the resource ids + | textfile0.txt | + | textfile1.txt | + | my_data | + | more_data | + Then the HTTP status code should be "200" + And the downloaded tar archive should contain these files: + | name | content | + | textfile0.txt | some data | + | textfile1.txt | other data | + | my_data/textfile2.txt | some data | + | more_data/an_other_file.txt | more data | + + + Scenario: download a single file as different user + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + When user "Brian" downloads the archive of "/textfile0.txt" of user "Alice" using the resource id + Then the HTTP status code should be "400" + + + Scenario: download multiple shared items as share receiver + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has uploaded file with content "other data" to "/textfile1.txt" + And user "Alice" has created folder "my_data" + And user "Alice" has uploaded file with content "some data" to "/my_data/textfile2.txt" + And user "Alice" has created folder "more_data" + And user "Alice" has uploaded file with content "more data" to "/more_data/an_other_file.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Alice" has shared file "textfile1.txt" with user "Brian" + And user "Alice" has shared folder "my_data" with user "Brian" + And user "Alice" has shared folder "more_data" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has accepted share "/textfile1.txt" offered by user "Alice" + And user "Brian" has accepted share "/my_data" offered by user "Alice" + And user "Brian" has accepted share "/more_data" offered by user "Alice" + When user "Brian" downloads the archive of these items using the resource ids + | /Shares/textfile0.txt | + | /Shares/textfile1.txt | + | /Shares/my_data | + | /Shares/more_data | + Then the HTTP status code should be "200" + And the downloaded tar archive should contain these files: + | name | content | + | textfile0.txt | some data | + | textfile1.txt | other data | + | my_data/textfile2.txt | some data | + | more_data/an_other_file.txt | more data | + + + Scenario Outline: download the Shares folder as share receiver + Given user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "some data" to "/textfile0.txt" + And user "Alice" has uploaded file with content "other data" to "/textfile1.txt" + And user "Alice" has created folder "my_data" + And user "Alice" has uploaded file with content "some data" to "/my_data/textfile2.txt" + And user "Alice" has created folder "more_data" + And user "Alice" has uploaded file with content "more data" to "/more_data/an_other_file.txt" + And user "Alice" has shared file "textfile0.txt" with user "Brian" + And user "Alice" has shared file "textfile1.txt" with user "Brian" + And user "Alice" has shared folder "my_data" with user "Brian" + And user "Alice" has shared folder "more_data" with user "Brian" + And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice" + And user "Brian" has accepted share "/textfile1.txt" offered by user "Alice" + And user "Brian" has accepted share "/my_data" offered by user "Alice" + And user "Brian" has accepted share "/more_data" offered by user "Alice" + When user "Brian" downloads the archive of "/Shares" using the resource id and setting these headers + | header | value | + | User-Agent | | + Then the HTTP status code should be "200" + And the downloaded archive should contain these files: + | name | content | + | Shares/textfile0.txt | some data | + | Shares/textfile1.txt | other data | + | Shares/my_data/textfile0.txt | some data | + | Shares/my_data/an_other_file.txt | more data | + Examples: + | user-agent | archive-type | + | Linux | tar | + | Windows NT | zip | diff --git a/tests/acceptance/features/apiSpaces/uploadSpaces.feature b/tests/acceptance/features/apiSpaces/uploadSpaces.feature index 721d5345a..4a0da5ea3 100644 --- a/tests/acceptance/features/apiSpaces/uploadSpaces.feature +++ b/tests/acceptance/features/apiSpaces/uploadSpaces.feature @@ -16,19 +16,16 @@ Feature: Upload files into a space And user "Alice" lists all available spaces via the GraphApi And user "Alice" creates a folder "mainFolder" in space "Project Venus" using the WebDav Api Then the HTTP status code should be "201" - When user "Alice" lists the content of the space with the name "Project Venus" using the WebDav Api - Then the propfind result of the space should contain these entries: + And the space "Project Venus" should contain these entries: | mainFolder | - Scenario: Bob creates a folder via the Graph api in a space, he expects a 404 code and - Alice checks that this folder does not exist + Scenario: Bob creates a folder via the Graph api in a space, he expects a 404 code and Alice checks that this folder does not exist Given the administrator gives "Alice" the role "Admin" using the settings api When user "Alice" creates a space "Project Merkur" of type "project" with quota "2000" using the GraphApi And user "Alice" lists all available spaces via the GraphApi And user "Bob" creates a folder "forAlice" in space "Project Merkur" using the WebDav Api Then the HTTP status code should be "404" - When user "Alice" lists the content of the space with the name "Project Merkur" using the WebDav Api - Then the propfind result of the space should not contain these entries: + And the space "Project Merkur" should not contain these entries: | forAlice | Scenario: Alice creates a folder via Graph api and uploads a file @@ -39,20 +36,17 @@ Feature: Upload files into a space Then the HTTP status code should be "201" And user "Alice" uploads a file inside space "Project Moon" with content "Test" to "test.txt" using the WebDAV API Then the HTTP status code should be "201" - When user "Alice" lists the content of the space with the name "Project Moon" using the WebDav Api - Then the propfind result of the space should contain these entries: + And the space "Project Moon" should contain these entries: | NewFolder | | test.txt | - Scenario: Bob uploads a file via the Graph api in a space, he expects a 404 code and - Alice checks that this file does not exist + Scenario: Bob uploads a file via the Graph api in a space, he expects a 404 code and Alice checks that this file does not exist Given the administrator gives "Alice" the role "Admin" using the settings api When user "Alice" creates a space "Project Pluto" of type "project" with quota "2000" using the GraphApi And user "Alice" lists all available spaces via the GraphApi And user "Bob" uploads a file inside space "Project Pluto" with content "Test" to "test.txt" using the WebDAV API Then the HTTP status code should be "404" - When user "Alice" lists the content of the space with the name "Project Pluto" using the WebDav Api - Then the propfind result of the space should not contain these entries: + And the space "Project Pluto" should not contain these entries: | test.txt | Scenario: Alice creates uploads a file and checks her quota diff --git a/tests/acceptance/features/bootstrap/ArchiverContext.php b/tests/acceptance/features/bootstrap/ArchiverContext.php index 4b737f302..f2c5f9ed9 100644 --- a/tests/acceptance/features/bootstrap/ArchiverContext.php +++ b/tests/acceptance/features/bootstrap/ArchiverContext.php @@ -63,16 +63,29 @@ class ArchiverContext implements Context { } /** - * @When user :user downloads the archive of :resourceId using the resource id + * @When user :user downloads the archive of :resourceId using the resource id and setting these headers * * @param string $user * @param string $resource + * @param TableNode $headersTable * * @return void * * @throws \GuzzleHttp\Exception\GuzzleException */ - public function userDownloadsTheArchiveOfUsingTheResourceId(string $user, string $resource): void { + public function userDownloadsTheArchiveOfUsingTheResourceId( + string $user, + string $resource, + TableNode $headersTable + ): void { + $this->featureContext->verifyTableNodeColumns( + $headersTable, + ['header', 'value'] + ); + $headers = []; + foreach ($headersTable as $row) { + $headers[$row['header']] = $row ['value']; + } $resourceId = $this->featureContext->getFileIdForPath($user, $resource); $user = $this->featureContext->getActualUsername($user); $this->featureContext->setResponse( @@ -80,25 +93,86 @@ class ArchiverContext implements Context { $this->featureContext->getBaseUrl() . '/archiver?id=' . $resourceId, '', $user, - $this->featureContext->getPasswordForUser($user) + $this->featureContext->getPasswordForUser($user), + $headers ) ); } /** - * @Then the downloaded archive should contain these files: + * @When user :downloader downloads the archive of :item of user :owner using the resource id * + * @param string $downloader Who sends the request + * @param string $resource + * @param string $owner Who is the real owner of the file + * + * @return void + * + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function userDownloadsTheArchiveOfItemOfUserUsingTheResourceId( + string $downloader, + string $resource, + string $owner + ): void { + $resourceId = $this->featureContext->getFileIdForPath($owner, $resource); + $downloader = $this->featureContext->getActualUsername($downloader); + $this->featureContext->setResponse( + HttpRequestHelper::get( + $this->featureContext->getBaseUrl() . '/archiver?id=' . $resourceId, + '', + $downloader, + $this->featureContext->getPasswordForUser($downloader), + ) + ); + } + + /** + * @When user :arg1 downloads the archive of these items using the resource ids + * + * @param string $user + * @param TableNode $items + * + * @return void + * + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function userDownloadsTheArchiveOfTheseItemsUsingTheResourceIds( + string $user, + TableNode $items + ): void { + $user = $this->featureContext->getActualUsername($user); + $resourceIdsString = ''; + foreach ($items->getRows() as $item) { + $fileId = $this->featureContext->getFileIdForPath($user, $item[0]); + $resourceIdsString .= 'id=' . $fileId . '&'; + } + $resourceIdsString = \rtrim($resourceIdsString, '&'); + $this->featureContext->setResponse( + HttpRequestHelper::get( + $this->featureContext->getBaseUrl() . '/archiver?' . $resourceIdsString, + '', + $user, + $this->featureContext->getPasswordForUser($user), + ) + ); + } + + /** + * @Then the downloaded :type archive should contain these files: + * + * @param string $type * @param TableNode $expectedFiles * * @return void * * @throws Exception */ - public function theDownloadedArchiveShouldContainTheseFiles(TableNode $expectedFiles) { + public function theDownloadedArchiveShouldContainTheseFiles(string $type, TableNode $expectedFiles) { $this->featureContext->verifyTableNodeColumns($expectedFiles, ['name', 'content']); $tempFile = \tempnam(\sys_get_temp_dir(), 'OcAcceptanceTests_'); \unlink($tempFile); // we only need the name - $tempFile = $tempFile . '.tar'; // it needs the extension + $tempFile = $tempFile . '.' . $type; // it needs the extension \file_put_contents($tempFile, $this->featureContext->getResponse()->getBody()->getContents()); $archive = UnifiedArchive::open($tempFile); foreach ($expectedFiles->getHash() as $expectedFile) { diff --git a/tests/acceptance/features/bootstrap/SpacesContext.php b/tests/acceptance/features/bootstrap/SpacesContext.php index 047c9505e..1731d74ec 100644 --- a/tests/acceptance/features/bootstrap/SpacesContext.php +++ b/tests/acceptance/features/bootstrap/SpacesContext.php @@ -20,7 +20,6 @@ * */ use Behat\Behat\Context\Context; -use Behat\Behat\Hook\Scope\AfterScenarioScope; use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Exception\GuzzleException; @@ -36,753 +35,815 @@ require_once 'bootstrap.php'; */ class SpacesContext implements Context { - /** - * @var FeatureContext - */ - private FeatureContext $featureContext; + /** + * @var FeatureContext + */ + private FeatureContext $featureContext; - /** - * @var array - */ - private array $availableSpaces; + /** + * @var array key is space name and value is the username that created the space + */ + private array $createdSpaces; - /** - * @return array - */ - public function getAvailableSpaces(): array { - return $this->availableSpaces; - } + /** + * @param string $spaceName + * + * @return string name of the user that created the space + * @throws Exception + */ + public function getSpaceCreator(string $spaceName): string { + if (!\array_key_exists($spaceName, $this->createdSpaces)) { + throw new Exception(__METHOD__ . " space '$spaceName' has not been created in this scenario"); + } + return $this->createdSpaces[$spaceName]; + } - /** - * @param array $availableSpaces - * - * @return void - */ - public function setAvailableSpaces(array $availableSpaces): void { - $this->availableSpaces = $availableSpaces; - } + /** + * @param string $spaceName + * @param string $spaceCreator + * + * @return void + */ + public function setSpaceCreator(string $spaceName, string $spaceCreator): void { + $this->createdSpaces[$spaceName] = $spaceCreator; + } - /** - * response content parsed from XML to an array - * - * @var array - */ - private array $responseXml = []; + /** + * @var array + */ + private array $availableSpaces; - /** - * @return array - */ - public function getResponseXml(): array { - return $this->responseXml; - } + /** + * @return array + */ + public function getAvailableSpaces(): array { + return $this->availableSpaces; + } - /** - * @param array $responseXml - * - * @return void - */ - public function setResponseXml(array $responseXml): void { - $this->responseXml = $responseXml; - } + /** + * @param array $availableSpaces + * + * @return void + */ + public function setAvailableSpaces(array $availableSpaces): void { + $this->availableSpaces = $availableSpaces; + } - /** - * space id from last propfind request - * - * @var string - */ - private string $responseSpaceId; + /** + * response content parsed from XML to an array + * + * @var array + */ + private array $responseXml = []; - /** - * @param string $responseSpaceId - * - * @return void - */ - public function setResponseSpaceId(string $responseSpaceId): void { - $this->responseSpaceId = $responseSpaceId; - } + /** + * @return array + */ + public function getResponseXml(): array { + return $this->responseXml; + } - /** - * @return string - */ - public function getResponseSpaceId(): string { - return $this->responseSpaceId; - } + /** + * @param array $responseXml + * + * @return void + */ + public function setResponseXml(array $responseXml): void { + $this->responseXml = $responseXml; + } - /** - * Get SpaceId by Name - * - * @param $name string - * @return string - * @throws Exception - */ - public function getSpaceIdByNameFromResponse(string $name): string - { - $space = $this->getSpaceByNameFromResponse($name); - Assert::assertIsArray($space, "Space with name $name not found"); - if (!isset($space["id"])) { - throw new Exception(__METHOD__ . " space with name $name not found"); - } - return $space["id"]; - } + /** + * space id from last propfind request + * + * @var string + */ + private string $responseSpaceId; - /** - * Get Space Array by name - * - * @param string $name - * @return array - * @throws Exception - */ - public function getSpaceByNameFromResponse(string $name): array - { - $response = json_decode($this->featureContext->getResponse()->getBody(), true, 512, JSON_THROW_ON_ERROR); - $spaceAsArray = $response; - if (isset($response['name']) && $response['name'] === $name) { - return $response; - } - foreach ($spaceAsArray["value"] as $spaceCandidate) { - if ($spaceCandidate['name'] === $name) { - return $spaceCandidate; - } - } - return []; - } + /** + * @param string $responseSpaceId + * + * @return void + */ + public function setResponseSpaceId(string $responseSpaceId): void { + $this->responseSpaceId = $responseSpaceId; + } - /** - * @param string $name - * @return array - */ - public function getSpaceByName(string $name): array { - $spaces = $this->getAvailableSpaces(); - Assert::assertIsArray($spaces[$name]); - return $spaces[$name]; - } + /** + * @return string + */ + public function getResponseSpaceId(): string { + return $this->responseSpaceId; + } - /** - * @BeforeScenario - * - * @param BeforeScenarioScope $scope - * - * @return void - * @throws Exception - */ - public function setUpScenario(BeforeScenarioScope $scope): void - { - // Get the environment - $environment = $scope->getEnvironment(); - // Get all the contexts you need in this context - $this->featureContext = $environment->getContext('FeatureContext'); - SetupHelper::init( - $this->featureContext->getAdminUsername(), - $this->featureContext->getAdminPassword(), - $this->featureContext->getBaseUrl(), - $this->featureContext->getOcPath() - ); - } + /** + * Get SpaceId by Name + * + * @param $name string + * + * @return string + * + * @throws Exception + */ + public function getSpaceIdByNameFromResponse(string $name): string { + $space = $this->getSpaceByNameFromResponse($name); + Assert::assertIsArray($space, "Space with name $name not found"); + if (!isset($space["id"])) { + throw new Exception(__METHOD__ . " space with name $name not found"); + } + return $space["id"]; + } - /** - * Send Graph List Spaces Request - * - * @param string $baseUrl - * @param string $user - * @param string $password - * @param string $urlArguments - * @param string $xRequestId - * @param array $body - * @param array $headers - * - * @return ResponseInterface - * - * @throws GuzzleException - */ - public function listSpacesRequest( - string $baseUrl, - string $user, - string $password, - string $urlArguments, - string $xRequestId = '', - array $body = [], - array $headers = [] - ): ResponseInterface { - $fullUrl = $baseUrl; - if (!str_ends_with($fullUrl, '/')) { - $fullUrl .= '/'; - } - $fullUrl .= "graph/v1.0/me/drives/" . $urlArguments; + /** + * Get Space Array by name + * + * @param string $name + * + * @return array + * + * @throws Exception + */ + public function getSpaceByNameFromResponse(string $name): array { + $response = json_decode($this->featureContext->getResponse()->getBody(), true, 512, JSON_THROW_ON_ERROR); + $spaceAsArray = $response; + if (isset($response['name']) && $response['name'] === $name) { + return $response; + } + foreach ($spaceAsArray["value"] as $spaceCandidate) { + if ($spaceCandidate['name'] === $name) { + return $spaceCandidate; + } + } + return []; + } - return HttpRequestHelper::get($fullUrl, $xRequestId, $user, $password, $headers, $body); - } + /** + * @param string $name + * + * @return array + */ + public function getSpaceByName(string $name): array { + $spaces = $this->getAvailableSpaces(); + Assert::assertIsArray($spaces[$name]); + return $spaces[$name]; + } - /** - * Send Graph Create Space Request - * - * @param string $baseUrl - * @param string $user - * @param string $password - * @param string $body - * @param string $xRequestId - * @param array $headers - * - * @return ResponseInterface - * - * @throws GuzzleException - */ - public function sendCreateSpaceRequest( - string $baseUrl, - string $user, - string $password, - string $body, - string $xRequestId = '', - array $headers = [] - ): ResponseInterface { - $fullUrl = $baseUrl; - if (!str_ends_with($fullUrl, '/')) { - $fullUrl .= '/'; - } - $fullUrl .= "graph/v1.0/drives/"; + /** + * @BeforeScenario + * + * @param BeforeScenarioScope $scope + * + * @return void + * + * @throws Exception + */ + public function setUpScenario(BeforeScenarioScope $scope): void { + // Get the environment + $environment = $scope->getEnvironment(); + // Get all the contexts you need in this context + $this->featureContext = $environment->getContext('FeatureContext'); + SetupHelper::init( + $this->featureContext->getAdminUsername(), + $this->featureContext->getAdminPassword(), + $this->featureContext->getBaseUrl(), + $this->featureContext->getOcPath() + ); + } - return HttpRequestHelper::post($fullUrl, $xRequestId, $user, $password, $headers, $body); - } + /** + * Send Graph List Spaces Request + * + * @param string $baseUrl + * @param string $user + * @param string $password + * @param string $urlArguments + * @param string $xRequestId + * @param array $body + * @param array $headers + * + * @return ResponseInterface + * + * @throws GuzzleException + */ + public function listSpacesRequest( + string $baseUrl, + string $user, + string $password, + string $urlArguments, + string $xRequestId = '', + array $body = [], + array $headers = [] + ): ResponseInterface { + $fullUrl = $baseUrl; + if (!str_ends_with($fullUrl, '/')) { + $fullUrl .= '/'; + } + $fullUrl .= "graph/v1.0/me/drives/" . $urlArguments; - /** - * Send Propfind Request to Url - * - * @param string $fullUrl - * @param string $user - * @param string $password - * @param string $xRequestId - * @param array $headers - * - * @return ResponseInterface - * - * @throws GuzzleException - */ - public function sendPropfindRequestToUrl( - string $fullUrl, - string $user, - string $password, - string $xRequestId = '', - array $headers = [] - ): ResponseInterface { - return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, 'PROPFIND', $user, $password, $headers); - } + return HttpRequestHelper::get($fullUrl, $xRequestId, $user, $password, $headers, $body); + } - /** - * Send Put Request to Url - * - * @param string $fullUrl - * @param string $user - * @param string $password - * @param string $xRequestId - * @param array $headers - * @param string $content - * @return ResponseInterface - * @throws GuzzleException - */ - public function sendPutRequestToUrl( - string $fullUrl, - string $user, - string $password, - string $xRequestId = '', - array $headers = [], - string $content = "" - ): ResponseInterface - { - return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, 'PUT', $user, $password, $headers, $content); - } + /** + * Send Graph Create Space Request + * + * @param string $baseUrl + * @param string $user + * @param string $password + * @param string $body + * @param string $xRequestId + * @param array $headers + * + * @return ResponseInterface + * + * @throws GuzzleException + */ + public function sendCreateSpaceRequest( + string $baseUrl, + string $user, + string $password, + string $body, + string $xRequestId = '', + array $headers = [] + ): ResponseInterface { + $fullUrl = $baseUrl; + if (!str_ends_with($fullUrl, '/')) { + $fullUrl .= '/'; + } + $fullUrl .= "graph/v1.0/drives/"; - /** - * @When /^user "([^"]*)" lists all available spaces via the GraphApi$/ - * - * @param string $user - * @return void - * @throws GuzzleException - */ - public function theUserListsAllHisAvailableSpacesUsingTheGraphApi(string $user): void - { - $this->featureContext->setResponse( - $this->listSpacesRequest( - $this->featureContext->getBaseUrl(), - $user, - $this->featureContext->getPasswordForUser($user), - "", - "" - ) - ); - $this->rememberTheAvailableSpaces(); - } + return HttpRequestHelper::post($fullUrl, $xRequestId, $user, $password, $headers, $body); + } - /** - * @When /^user "([^"]*)" creates a space "([^"]*)" of type "([^"]*)" with the default quota using the GraphApi$/ - * - * @param string $user - * @param string $spaceName - * @param string $spaceType - * - * @return void - * - * @throws GuzzleException - * @throws Exception - */ - public function theUserCreatesASpaceUsingTheGraphApi( - string $user, - string $spaceName, - string $spaceType - ): void { - $space = ["Name" => $spaceName, "driveType" => $spaceType]; - $body = json_encode($space, JSON_THROW_ON_ERROR); - $this->featureContext->setResponse( - $this->sendCreateSpaceRequest( - $this->featureContext->getBaseUrl(), - $user, - $this->featureContext->getPasswordForUser($user), - $body, - "" - ) - ); - } + /** + * Send Propfind Request to Url + * + * @param string $fullUrl + * @param string $user + * @param string $password + * @param string $xRequestId + * @param array $headers + * + * @return ResponseInterface + * + * @throws GuzzleException + */ + public function sendPropfindRequestToUrl( + string $fullUrl, + string $user, + string $password, + string $xRequestId = '', + array $headers = [] + ): ResponseInterface { + return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, 'PROPFIND', $user, $password, $headers); + } - /** - * @When /^user "([^"]*)" creates a space "([^"]*)" of type "([^"]*)" with quota "([^"]*)" using the GraphApi$/ - * - * @param string $user - * @param string $spaceName - * @param string $spaceType - * @param int $quota - * - * @return void - * - * @throws GuzzleException - * @throws Exception - */ - public function theUserCreatesASpaceWithQuotaUsingTheGraphApi( - string $user, - string $spaceName, - string $spaceType, - int $quota - ): void { - $space = ["Name" => $spaceName, "driveType" => $spaceType, "quota" => ["total" => $quota]]; - $body = json_encode($space); - $this->featureContext->setResponse( - $this->sendCreateSpaceRequest( - $this->featureContext->getBaseUrl(), - $user, - $this->featureContext->getPasswordForUser($user), - $body, - "" - ) - ); - } + /** + * Send Put Request to Url + * + * @param string $fullUrl + * @param string $user + * @param string $password + * @param string $xRequestId + * @param array $headers + * @param string $content + * + * @return ResponseInterface + * + * @throws GuzzleException + */ + public function sendPutRequestToUrl( + string $fullUrl, + string $user, + string $password, + string $xRequestId = '', + array $headers = [], + string $content = "" + ): ResponseInterface { + return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, 'PUT', $user, $password, $headers, $content); + } - /** - * @When /^the administrator gives "([^"]*)" the role "([^"]*)" using the settings api$/ - * - * @param string $user - * @param string $role - * - * @return void - * - * @throws GuzzleException - * @throws Exception - */ - public function theAdministratorGivesUserTheRole(string $user, string $role): void { - $admin = $this->featureContext->getAdminUsername(); - $password = $this->featureContext->getAdminPassword(); - $headers = []; - $bundles = []; - $accounts = []; - $assignment = []; + /** + * @When /^user "([^"]*)" lists all available spaces via the GraphApi$/ + * + * @param string $user + * + * @return void + * + * @throws GuzzleException + */ + public function theUserListsAllHisAvailableSpacesUsingTheGraphApi(string $user): void { + $this->featureContext->setResponse( + $this->listSpacesRequest( + $this->featureContext->getBaseUrl(), + $user, + $this->featureContext->getPasswordForUser($user), + "", + "" + ) + ); + $this->rememberTheAvailableSpaces(); + } - $baseUrl = $this->featureContext->getBaseUrl(); - if (!str_ends_with($baseUrl, '/')) { - $baseUrl .= '/'; - } - // get the roles list first - $fullUrl = $baseUrl . "api/v0/settings/roles-list"; - $this->featureContext->setResponse(HttpRequestHelper::post($fullUrl, "", $admin, $password, $headers, "{}")); - if ($this->featureContext->getResponse()) { - $rawBody = $this->featureContext->getResponse()->getBody()->getContents(); - if (isset(\json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["bundles"])) { - $bundles = \json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["bundles"]; - } - } - $roleToAssign = ""; - foreach ($bundles as $value) { - // find the selected role - if ($value["displayName"] === $role) { - $roleToAssign = $value; - } - } - Assert::assertNotEmpty($roleToAssign, "The selected role $role could not be found"); + /** + * @When /^user "([^"]*)" creates a space "([^"]*)" of type "([^"]*)" with the default quota using the GraphApi$/ + * + * @param string $user + * @param string $spaceName + * @param string $spaceType + * + * @return void + * + * @throws GuzzleException + * @throws Exception + */ + public function theUserCreatesASpaceUsingTheGraphApi( + string $user, + string $spaceName, + string $spaceType + ): void { + $space = ["Name" => $spaceName, "driveType" => $spaceType]; + $body = json_encode($space, JSON_THROW_ON_ERROR); + $this->featureContext->setResponse( + $this->sendCreateSpaceRequest( + $this->featureContext->getBaseUrl(), + $user, + $this->featureContext->getPasswordForUser($user), + $body, + "" + ) + ); + $this->setSpaceCreator($spaceName, $user); + } - // get the accounts list first - $fullUrl = $baseUrl . "api/v0/accounts/accounts-list"; - $this->featureContext->setResponse(HttpRequestHelper::post($fullUrl, "", $admin, $password, $headers, "{}")); - if ($this->featureContext->getResponse()) { - $rawBody = $this->featureContext->getResponse()->getBody()->getContents(); - if (isset(\json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["accounts"])) { - $accounts = \json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["accounts"]; - } - } - $accountToChange = ""; - foreach ($accounts as $account) { - // find the selected user - if ($account["preferredName"] === $user) { - $accountToChange = $account; - } - } - Assert::assertNotEmpty($accountToChange, "The selected account $user does not exist"); + /** + * @When /^user "([^"]*)" creates a space "([^"]*)" of type "([^"]*)" with quota "([^"]*)" using the GraphApi$/ + * + * @param string $user + * @param string $spaceName + * @param string $spaceType + * @param int $quota + * + * @return void + * + * @throws GuzzleException + * @throws Exception + */ + public function theUserCreatesASpaceWithQuotaUsingTheGraphApi( + string $user, + string $spaceName, + string $spaceType, + int $quota + ): void { + $space = ["Name" => $spaceName, "driveType" => $spaceType, "quota" => ["total" => $quota]]; + $body = json_encode($space); + $this->featureContext->setResponse( + $this->sendCreateSpaceRequest( + $this->featureContext->getBaseUrl(), + $user, + $this->featureContext->getPasswordForUser($user), + $body, + "" + ) + ); + $this->setSpaceCreator($spaceName, $user); + } - // set the new role - $fullUrl = $baseUrl . "api/v0/settings/assignments-add"; - $body = json_encode(["account_uuid" => $accountToChange["id"], "role_id" => $roleToAssign["id"]], JSON_THROW_ON_ERROR); + /** + * @When /^the administrator gives "([^"]*)" the role "([^"]*)" using the settings api$/ + * + * @param string $user + * @param string $role + * + * @return void + * + * @throws GuzzleException + * @throws Exception + */ + public function theAdministratorGivesUserTheRole(string $user, string $role): void { + $admin = $this->featureContext->getAdminUsername(); + $password = $this->featureContext->getAdminPassword(); + $headers = []; + $bundles = []; + $accounts = []; + $assignment = []; - $this->featureContext->setResponse(HttpRequestHelper::post($fullUrl, "", $admin, $password, $headers, $body)); - if ($this->featureContext->getResponse()) { - $rawBody = $this->featureContext->getResponse()->getBody()->getContents(); - if (isset(\json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["assignment"])) { - $assignment = \json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["assignment"]; - } - } + $baseUrl = $this->featureContext->getBaseUrl(); + if (!str_ends_with($baseUrl, '/')) { + $baseUrl .= '/'; + } + // get the roles list first + $fullUrl = $baseUrl . "api/v0/settings/roles-list"; + $this->featureContext->setResponse(HttpRequestHelper::post($fullUrl, "", $admin, $password, $headers, "{}")); + if ($this->featureContext->getResponse()) { + $rawBody = $this->featureContext->getResponse()->getBody()->getContents(); + if (isset(\json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["bundles"])) { + $bundles = \json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["bundles"]; + } + } + $roleToAssign = ""; + foreach ($bundles as $value) { + // find the selected role + if ($value["displayName"] === $role) { + $roleToAssign = $value; + } + } + Assert::assertNotEmpty($roleToAssign, "The selected role $role could not be found"); - Assert::assertEquals($accountToChange["id"], $assignment["accountUuid"]); - Assert::assertEquals($roleToAssign["id"], $assignment["roleId"]); - } + // get the accounts list first + $fullUrl = $baseUrl . "api/v0/accounts/accounts-list"; + $this->featureContext->setResponse(HttpRequestHelper::post($fullUrl, "", $admin, $password, $headers, "{}")); + if ($this->featureContext->getResponse()) { + $rawBody = $this->featureContext->getResponse()->getBody()->getContents(); + if (isset(\json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["accounts"])) { + $accounts = \json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["accounts"]; + } + } + $accountToChange = ""; + foreach ($accounts as $account) { + // find the selected user + if ($account["preferredName"] === $user) { + $accountToChange = $account; + } + } + Assert::assertNotEmpty($accountToChange, "The selected account $user does not exist"); - /** - * Remember the available Spaces - * - * @return void - * - * @throws Exception - */ - public function rememberTheAvailableSpaces(): void { - $rawBody = $this->featureContext->getResponse()->getBody()->getContents(); - $drives = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR); - if (isset($drives["value"])) { - $drives = $drives["value"]; - } + // set the new role + $fullUrl = $baseUrl . "api/v0/settings/assignments-add"; + $body = json_encode(["account_uuid" => $accountToChange["id"], "role_id" => $roleToAssign["id"]], JSON_THROW_ON_ERROR); - Assert::assertArrayHasKey(0, $drives, "No drives were found on that endpoint"); - $spaces = []; - foreach ($drives as $drive) { - $spaces[$drive["name"]] = $drive; - } - $this->setAvailableSpaces($spaces); - Assert::assertNotEmpty($spaces, "No spaces have been found"); - } + $this->featureContext->setResponse(HttpRequestHelper::post($fullUrl, "", $admin, $password, $headers, $body)); + if ($this->featureContext->getResponse()) { + $rawBody = $this->featureContext->getResponse()->getBody()->getContents(); + if (isset(\json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["assignment"])) { + $assignment = \json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR)["assignment"]; + } + } - /** - * @When /^user "([^"]*)" lists the content of the space with the name "([^"]*)" using the WebDav Api$/ - * - * @param string $user - * @param string $name - * @return void - * @throws GuzzleException - */ - public function theUserListsTheContentOfAPersonalSpaceRootUsingTheWebDAvApi( - string $user, - string $name - ): void - { - $space = $this->getSpaceByName($name); - Assert::assertIsArray($space); - Assert::assertNotEmpty($spaceId = $space["id"]); - Assert::assertNotEmpty($spaceWebDavUrl = $space["root"]["webDavUrl"]); - $this->featureContext->setResponse( - $this->sendPropfindRequestToUrl( - $spaceWebDavUrl, - $user, - $this->featureContext->getPasswordForUser($user), - "", - [], - ) - ); - $this->setResponseSpaceId($spaceId); - $this->setResponseXml(HttpRequestHelper::parseResponseAsXml($this->featureContext->getResponse()) - ); - } + Assert::assertEquals($accountToChange["id"], $assignment["accountUuid"]); + Assert::assertEquals($roleToAssign["id"], $assignment["roleId"]); + } - /** - * @Then /^the (?:propfind|search) result of the space should (not|)\s?contain these (?:files|entries):$/ - * - * @param string $shouldOrNot (not|) - * @param TableNode $expectedFiles - * - * @return void - * - * @throws Exception - */ - public function thePropfindResultShouldContainEntries( - string $shouldOrNot, - TableNode $expectedFiles - ):void { - $this->propfindResultShouldContainEntries( - $shouldOrNot, - $expectedFiles, - ); - } + /** + * Remember the available Spaces + * + * @return void + * + * @throws Exception + */ + public function rememberTheAvailableSpaces(): void { + $rawBody = $this->featureContext->getResponse()->getBody()->getContents(); + $drives = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR); + if (isset($drives["value"])) { + $drives = $drives["value"]; + } - /** - * @Then /^the json responded should contain a space "([^"]*)" with these key and value pairs:$/ - * - * @param string $spaceName - * @param TableNode $table - * - * @return void - * @throws Exception - */ - public function jsonRespondedShouldContain( - string $spaceName, - TableNode $table - ): void { - $this->featureContext->verifyTableNodeColumns($table, ['key', 'value']); - Assert::assertIsArray($spaceAsArray = $this->getSpaceByNameFromResponse($spaceName), "No space with name $spaceName found"); - foreach ($table->getHash() as $row) { - // remember the original Space Array - $original = $spaceAsArray; - $row['value'] = $this->featureContext->substituteInLineCodes( - $row['value'], - $this->featureContext->getCurrentUser(), - [], - [ - [ - "code" => "%space_id%", - "function" => - [$this, "getSpaceIdByNameFromResponse"], - "parameter" => [$spaceName] - ] - ] - ); - $segments = explode("@@@", $row["key"]); - // traverse down in the array - foreach ($segments as $segment) { - $arrayKeyExists = array_key_exists($segment, $spaceAsArray); - $key = $row["key"]; - Assert::assertTrue($arrayKeyExists, "The key $key does not exist on the response"); - if ($arrayKeyExists) { - $spaceAsArray = $spaceAsArray[$segment]; - } - } - Assert::assertEquals($row["value"], $spaceAsArray); - // set the spaceArray to the point before traversing - $spaceAsArray = $original; - } - } + Assert::assertArrayHasKey(0, $drives, "No drives were found on that endpoint"); + $spaces = []; + foreach ($drives as $drive) { + $spaces[$drive["name"]] = $drive; + } + $this->setAvailableSpaces($spaces); + Assert::assertNotEmpty($spaces, "No spaces have been found"); + } - /** - * @param string $shouldOrNot (not|) - * @param TableNode $expectedFiles - * - * @return void - * - * @throws Exception - */ - public function propfindResultShouldContainEntries( - string $shouldOrNot, - TableNode $expectedFiles - ): void { - $this->verifyTableNodeColumnsCount($expectedFiles, 1); - $elementRows = $expectedFiles->getRows(); - $should = ($shouldOrNot !== "not"); + /** + * @When /^user "([^"]*)" lists the content of the space with the name "([^"]*)" using the WebDav Api$/ + * + * @param string $user + * @param string $spaceName + * + * @return void + * + * @throws GuzzleException + */ + public function theUserListsTheContentOfAPersonalSpaceRootUsingTheWebDAvApi( + string $user, + string $spaceName + ): void { + $space = $this->getSpaceByName($spaceName); + Assert::assertIsArray($space); + Assert::assertNotEmpty($spaceId = $space["id"]); + Assert::assertNotEmpty($spaceWebDavUrl = $space["root"]["webDavUrl"]); + $this->featureContext->setResponse( + $this->sendPropfindRequestToUrl( + $spaceWebDavUrl, + $user, + $this->featureContext->getPasswordForUser($user), + "", + [], + ) + ); + $this->setResponseSpaceId($spaceId); + $this->setResponseXml( + HttpRequestHelper::parseResponseAsXml($this->featureContext->getResponse()) + ); + } - foreach ($elementRows as $expectedFile) { - $fileFound = $this->findEntryFromPropfindResponse( - $expectedFile[0] - ); - if ($should) { - Assert::assertNotEmpty( - $fileFound, - "response does not contain the entry '$expectedFile[0]'" - ); - } else { - Assert::assertEmpty( - $fileFound, - "response does contain the entry '$expectedFile[0]' but should not" - ); - } - } - } + /** + * @Then /^the (?:propfind|search) result of the space should (not|)\s?contain these (?:files|entries):$/ + * + * @param string $shouldOrNot (not|) + * @param TableNode $expectedFiles + * + * @return void + * + * @throws Exception + */ + public function thePropfindResultShouldContainEntries( + string $shouldOrNot, + TableNode $expectedFiles + ):void { + $this->propfindResultShouldContainEntries( + $shouldOrNot, + $expectedFiles, + ); + } - /** - * Verify that the tableNode contains expected number of columns - * - * @param TableNode $table - * @param int $count - * - * @return void - * - * @throws Exception - */ - public function verifyTableNodeColumnsCount( - TableNode $table, - int $count - ): void { - if (\count($table->getRows()) < 1) { - throw new Exception("Table should have at least one row."); - } - $rowCount = \count($table->getRows()[0]); - if ($count !== $rowCount) { - throw new Exception("Table expected to have $count rows but found $rowCount"); - } - } + /** + * @Then /^the space "([^"]*)" should (not|)\s?contain these (?:files|entries):$/ + * + * @param string $spaceName + * @param string $shouldOrNot (not|) + * @param TableNode $expectedFiles + * + * @return void + * + * @throws Exception|GuzzleException + */ + public function theSpaceShouldContainEntries( + string $spaceName, + string $shouldOrNot, + TableNode $expectedFiles + ):void { + $this->theUserListsTheContentOfAPersonalSpaceRootUsingTheWebDAvApi( + $this->getSpaceCreator($spaceName), + $spaceName + ); + $this->propfindResultShouldContainEntries( + $shouldOrNot, + $expectedFiles, + ); + } - /** - * parses a PROPFIND response from $this->response into xml - * and returns found search results if found else returns false - * - * @param string|null $entryNameToSearch - * - * @return array - * string if $entryNameToSearch is given and is found - * array if $entryNameToSearch is not given - * boolean false if $entryNameToSearch is given and is not found - */ - public function findEntryFromPropfindResponse( - string $entryNameToSearch = null - ): array { - $spaceId = $this->getResponseSpaceId(); - //if we are using that step the second time in a scenario e.g. 'But ... should not' - //then don't parse the result again, because the result in a ResponseInterface - if (empty($this->getResponseXml())) { - $this->setResponseXml( - HttpRequestHelper::parseResponseAsXml($this->featureContext->getResponse()) - ); - } - Assert::assertNotEmpty($this->getResponseXml(), __METHOD__ . ' Response is empty'); - Assert::assertNotEmpty($spaceId, __METHOD__ . ' SpaceId is empty'); + /** + * @Then /^the json responded should contain a space "([^"]*)" with these key and value pairs:$/ + * + * @param string $spaceName + * @param TableNode $table + * + * @return void + * @throws Exception + */ + public function jsonRespondedShouldContain( + string $spaceName, + TableNode $table + ): void { + $this->featureContext->verifyTableNodeColumns($table, ['key', 'value']); + Assert::assertIsArray($spaceAsArray = $this->getSpaceByNameFromResponse($spaceName), "No space with name $spaceName found"); + foreach ($table->getHash() as $row) { + // remember the original Space Array + $original = $spaceAsArray; + $row['value'] = $this->featureContext->substituteInLineCodes( + $row['value'], + $this->featureContext->getCurrentUser(), + [], + [ + [ + "code" => "%space_id%", + "function" => + [$this, "getSpaceIdByNameFromResponse"], + "parameter" => [$spaceName] + ] + ] + ); + $segments = explode("@@@", $row["key"]); + // traverse down in the array + foreach ($segments as $segment) { + $arrayKeyExists = \array_key_exists($segment, $spaceAsArray); + $key = $row["key"]; + Assert::assertTrue($arrayKeyExists, "The key $key does not exist on the response"); + if ($arrayKeyExists) { + $spaceAsArray = $spaceAsArray[$segment]; + } + } + Assert::assertEquals($row["value"], $spaceAsArray); + // set the spaceArray to the point before traversing + $spaceAsArray = $original; + } + } - // trim any leading "/" passed by the caller, we can just match the "raw" name - $trimmedEntryNameToSearch = \trim($entryNameToSearch, "/"); + /** + * @param string $shouldOrNot (not|) + * @param TableNode $expectedFiles + * + * @return void + * + * @throws Exception + */ + public function propfindResultShouldContainEntries( + string $shouldOrNot, + TableNode $expectedFiles + ): void { + $this->verifyTableNodeColumnsCount($expectedFiles, 1); + $elementRows = $expectedFiles->getRows(); + $should = ($shouldOrNot !== "not"); - // topWebDavPath should be something like /remote.php/webdav/ or - // /remote.php/dav/files/alice/ - $topWebDavPath = "/" . "dav/spaces/" . $spaceId . "/"; + foreach ($elementRows as $expectedFile) { + $fileFound = $this->findEntryFromPropfindResponse( + $expectedFile[0] + ); + if ($should) { + Assert::assertNotEmpty( + $fileFound, + "response does not contain the entry '$expectedFile[0]'" + ); + } else { + Assert::assertEmpty( + $fileFound, + "response does contain the entry '$expectedFile[0]' but should not" + ); + } + } + } - Assert::assertIsArray( - $this->responseXml, - __METHOD__ . " responseXml for space $spaceId is not an array" - ); - Assert::assertArrayHasKey( - "value", - $this->responseXml, - __METHOD__ . " responseXml for space $spaceId does not have key 'value'" - ); - $multistatusResults = $this->responseXml["value"]; - $results = []; - if ($multistatusResults !== null) { - foreach ($multistatusResults as $multistatusResult) { - $entryPath = $multistatusResult['value'][0]['value']; - $entryName = \str_replace($topWebDavPath, "", $entryPath); - $entryName = \rawurldecode($entryName); - $entryName = \trim($entryName, "/"); - if ($trimmedEntryNameToSearch === $entryName) { - return $multistatusResult; - } - $results[] = $entryName; - } - } - if ($entryNameToSearch === null) { - return $results; - } - return []; - } + /** + * Verify that the tableNode contains expected number of columns + * + * @param TableNode $table + * @param int $count + * + * @return void + * + * @throws Exception + */ + public function verifyTableNodeColumnsCount( + TableNode $table, + int $count + ): void { + if (\count($table->getRows()) < 1) { + throw new Exception("Table should have at least one row."); + } + $rowCount = \count($table->getRows()[0]); + if ($count !== $rowCount) { + throw new Exception("Table expected to have $count rows but found $rowCount"); + } + } - /** - * @When /^user "([^"]*)" creates a folder "([^"]*)" in space "([^"]*)" using the WebDav Api$/ - * - * @param string $user - * @param string $folder - * @param string $spaceName - * - * @return void - * - * @throws GuzzleException - */ - public function theUserCreatesAFolderUsingTheGraphApi( - string $user, - string $folder, - string $spaceName - ): void { - $this->featureContext->setResponse( - $this->sendCreateFolderRequest( - $this->featureContext->getBaseUrl(), - "MKCOL", - $user, - $this->featureContext->getPasswordForUser($user), - $folder, - $spaceName - ) - ); - } + /** + * parses a PROPFIND response from $this->response into xml + * and returns found search results if found else returns false + * + * @param string|null $entryNameToSearch + * + * @return array + * string if $entryNameToSearch is given and is found + * array if $entryNameToSearch is not given + * boolean false if $entryNameToSearch is given and is not found + */ + public function findEntryFromPropfindResponse( + string $entryNameToSearch = null + ): array { + $spaceId = $this->getResponseSpaceId(); + //if we are using that step the second time in a scenario e.g. 'But ... should not' + //then don't parse the result again, because the result in a ResponseInterface + if (empty($this->getResponseXml())) { + $this->setResponseXml( + HttpRequestHelper::parseResponseAsXml($this->featureContext->getResponse()) + ); + } + Assert::assertNotEmpty($this->getResponseXml(), __METHOD__ . ' Response is empty'); + Assert::assertNotEmpty($spaceId, __METHOD__ . ' SpaceId is empty'); - /** - * @When /^user "([^"]*)" uploads a file inside space "([^"]*)" with content "([^"]*)" to "([^"]*)" using the WebDAV API$/ - * - * @param string $user - * @param string $spaceName - * @param string $content - * @param string $destination - * - * @return void - * @throws GuzzleException - * @throws Exception - */ - public function theUserUploadsAFileToSpace( - string $user, - string $spaceName, - string $content, - string $destination - ): void - { - $space = $this->getSpaceByName($spaceName); - Assert::assertIsArray($space, "Space with name $spaceName not found"); - Assert::assertNotEmpty($space["root"]["webDavUrl"], "WebDavUrl for space with name $spaceName not found"); + // trim any leading "/" passed by the caller, we can just match the "raw" name + $trimmedEntryNameToSearch = \trim($entryNameToSearch, "/"); - $this->featureContext->setResponse( - $this->sendPutRequestToUrl( - $space["root"]["webDavUrl"] . "/" . $destination, - $user, - $this->featureContext->getPasswordForUser($user), - "", - [], - $content - ) - ); - } + // topWebDavPath should be something like /remote.php/webdav/ or + // /remote.php/dav/files/alice/ + $topWebDavPath = "/" . "dav/spaces/" . $spaceId . "/"; - /** - * Send Graph Create Folder Request - * - * @param string $baseUrl - * @param string $method - * @param string $user - * @param string $password - * @param string $folder - * @param string $spaceName - * @param string $xRequestId - * @param array $headers - * - * @return ResponseInterface - * - * @throws GuzzleException - */ - public function sendCreateFolderRequest( - string $baseUrl, - string $method, - string $user, - string $password, - string $folder, - string $spaceName, - string $xRequestId = '', - array $headers = [] - ): ResponseInterface { - $spaceId = $this->getAvailableSpaces()[$spaceName]["id"]; - $fullUrl = $baseUrl; - if (!str_ends_with($fullUrl, '/')) { - $fullUrl .= '/'; - } - $fullUrl .= "dav/spaces/" . $spaceId . '/' . $folder; + Assert::assertIsArray( + $this->responseXml, + __METHOD__ . " responseXml for space $spaceId is not an array" + ); + Assert::assertArrayHasKey( + "value", + $this->responseXml, + __METHOD__ . " responseXml for space $spaceId does not have key 'value'" + ); + $multistatusResults = $this->responseXml["value"]; + $results = []; + if ($multistatusResults !== null) { + foreach ($multistatusResults as $multistatusResult) { + $entryPath = $multistatusResult['value'][0]['value']; + $entryName = \str_replace($topWebDavPath, "", $entryPath); + $entryName = \rawurldecode($entryName); + $entryName = \trim($entryName, "/"); + if ($trimmedEntryNameToSearch === $entryName) { + return $multistatusResult; + } + $results[] = $entryName; + } + } + if ($entryNameToSearch === null) { + return $results; + } + return []; + } - return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, $method, $user, $password, $headers); - } + /** + * @When /^user "([^"]*)" creates a folder "([^"]*)" in space "([^"]*)" using the WebDav Api$/ + * + * @param string $user + * @param string $folder + * @param string $spaceName + * + * @return void + * + * @throws GuzzleException + */ + public function theUserCreatesAFolderUsingTheGraphApi( + string $user, + string $folder, + string $spaceName + ): void { + $this->featureContext->setResponse( + $this->sendCreateFolderRequest( + $this->featureContext->getBaseUrl(), + "MKCOL", + $user, + $this->featureContext->getPasswordForUser($user), + $folder, + $spaceName + ) + ); + } + + /** + * @When /^user "([^"]*)" uploads a file inside space "([^"]*)" with content "([^"]*)" to "([^"]*)" using the WebDAV API$/ + * + * @param string $user + * @param string $spaceName + * @param string $content + * @param string $destination + * + * @return void + * @throws GuzzleException + * @throws Exception + */ + public function theUserUploadsAFileToSpace( + string $user, + string $spaceName, + string $content, + string $destination + ): void { + $space = $this->getSpaceByName($spaceName); + Assert::assertIsArray($space, "Space with name $spaceName not found"); + Assert::assertNotEmpty($space["root"]["webDavUrl"], "WebDavUrl for space with name $spaceName not found"); + + $this->featureContext->setResponse( + $this->sendPutRequestToUrl( + $space["root"]["webDavUrl"] . "/" . $destination, + $user, + $this->featureContext->getPasswordForUser($user), + "", + [], + $content + ) + ); + } + + /** + * Send Graph Create Folder Request + * + * @param string $baseUrl + * @param string $method + * @param string $user + * @param string $password + * @param string $folder + * @param string $spaceName + * @param string $xRequestId + * @param array $headers + * + * @return ResponseInterface + * + * @throws GuzzleException + */ + public function sendCreateFolderRequest( + string $baseUrl, + string $method, + string $user, + string $password, + string $folder, + string $spaceName, + string $xRequestId = '', + array $headers = [] + ): ResponseInterface { + $spaceId = $this->getAvailableSpaces()[$spaceName]["id"]; + $fullUrl = $baseUrl; + if (!str_ends_with($fullUrl, '/')) { + $fullUrl .= '/'; + } + $fullUrl .= "dav/spaces/" . $spaceId . '/' . $folder; + + return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, $method, $user, $password, $headers); + } } diff --git a/thumbnails/pkg/config/config.go b/thumbnails/pkg/config/config.go index d8cb7bd48..07af52367 100644 --- a/thumbnails/pkg/config/config.go +++ b/thumbnails/pkg/config/config.go @@ -61,6 +61,7 @@ type Thumbnail struct { Resolutions []string `mapstructure:"resolutions"` FileSystemStorage FileSystemStorage `mapstructure:"filesystem_storage"` WebdavAllowInsecure bool `mapstructure:"webdav_allow_insecure"` + CS3AllowInsecure bool `mapstructure:"cs3_allow_insecure"` RevaGateway string `mapstructure:"reva_gateway"` WebdavNamespace string `mapstructure:"webdav_namespace"` } @@ -99,6 +100,7 @@ func DefaultConfig() *Config { WebdavAllowInsecure: true, RevaGateway: "127.0.0.1:9142", WebdavNamespace: "/home", + CS3AllowInsecure: false, }, } } diff --git a/thumbnails/pkg/server/grpc/server.go b/thumbnails/pkg/server/grpc/server.go index 866868d12..0c905c06e 100644 --- a/thumbnails/pkg/server/grpc/server.go +++ b/thumbnails/pkg/server/grpc/server.go @@ -42,7 +42,7 @@ func NewService(opts ...Option) grpc.Service { options.Logger, ), ), - svc.CS3Source(imgsource.NewCS3Source(gc)), + svc.CS3Source(imgsource.NewCS3Source(tconf, gc)), svc.CS3Client(gc), ) thumbnail = svc.NewInstrument(thumbnail, options.Metrics) diff --git a/thumbnails/pkg/thumbnail/imgsource/cs3.go b/thumbnails/pkg/thumbnail/imgsource/cs3.go index 68cf63750..072713978 100644 --- a/thumbnails/pkg/thumbnail/imgsource/cs3.go +++ b/thumbnails/pkg/thumbnail/imgsource/cs3.go @@ -12,6 +12,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/rhttp" + "github.com/owncloud/ocis/thumbnails/pkg/config" "github.com/pkg/errors" "google.golang.org/grpc/metadata" ) @@ -23,12 +24,14 @@ const ( ) type CS3 struct { - client gateway.GatewayAPIClient + client gateway.GatewayAPIClient + insecure bool } -func NewCS3Source(c gateway.GatewayAPIClient) CS3 { +func NewCS3Source(cfg config.Thumbnail, c gateway.GatewayAPIClient) CS3 { return CS3{ - client: c, + client: c, + insecure: cfg.CS3AllowInsecure, } } @@ -67,7 +70,9 @@ func (s CS3) Get(ctx context.Context, path string) (io.ReadCloser, error) { httpReq.Header.Set(revactx.TokenHeader, auth) httpReq.Header.Set(TokenTransportHeader, tk) - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ + InsecureSkipVerify: s.insecure, //nolint:gosec + } client := &http.Client{} resp, err := client.Do(httpReq) // nolint:bodyclose