From f3a6fda1a30ed5250fae02cfd1d80e3cb8999fa7 Mon Sep 17 00:00:00 2001 From: Viktor Scharf Date: Fri, 12 Jan 2024 10:29:44 +0100 Subject: [PATCH 001/112] change release template --- .github/release_template.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/release_template.md b/.github/release_template.md index 51312c295..70f9413ea 100644 --- a/.github/release_template.md +++ b/.github/release_template.md @@ -4,7 +4,9 @@ ### Prerequisites * [ ] DEV/QA: Kickoff meeting [Kickoff meeting] (https://confluence.owncloud.com/display/QA/Technical+release+3.0.0+Overview) -* [ ] DEV/QA: Define client versions and provide a list of new features +* [ ] DEV/QA: Define client versions and provide list of breaking changes for desktop/mobile team +* [ ] DEV/QA: Check new strings and align with clients +* [ ] DEV/DOCS: Create list of pending docks tasks * [ ] DEV: Create branch `release-x.x.x-rc.x` -> CODEFREEZE * [ ] DEV: bump ocis version in necessary files * [ ] DEV: `changelog/CHANGELOG.tmpl` @@ -53,6 +55,12 @@ ### After QA Phase * [ ] Brief company-wide heads up via mail @tbsbdr +* [ ] Create list of changed ENV vars and send to release-coordination@owncloud.com + * [ ] Variable Name + * [ ] Introduced in version + * [ ] Default Value + * [ ] Description + * [ ] dependencies with user other components * [ ] DEV: Create branch `release-x.x.x` * [ ] DEV: bump ocis version in necessary files * [ ] DEV: `ocis-pkg/version/version.go` From 08f21787e266e690fb26fe60df56922329cc4c90 Mon Sep 17 00:00:00 2001 From: Viktor Scharf Date: Mon, 22 Jan 2024 09:14:43 +0100 Subject: [PATCH 002/112] Update release_template.md --- .github/release_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/release_template.md b/.github/release_template.md index 70f9413ea..6d3bc4947 100644 --- a/.github/release_template.md +++ b/.github/release_template.md @@ -6,7 +6,7 @@ * [ ] DEV/QA: Kickoff meeting [Kickoff meeting] (https://confluence.owncloud.com/display/QA/Technical+release+3.0.0+Overview) * [ ] DEV/QA: Define client versions and provide list of breaking changes for desktop/mobile team * [ ] DEV/QA: Check new strings and align with clients -* [ ] DEV/DOCS: Create list of pending docks tasks +* [ ] DEV/DOCS: Create list of pending docs tasks * [ ] DEV: Create branch `release-x.x.x-rc.x` -> CODEFREEZE * [ ] DEV: bump ocis version in necessary files * [ ] DEV: `changelog/CHANGELOG.tmpl` From 754f6cc28a16a023bcf499f43b7394c04e2af5a5 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Mon, 3 Jul 2023 18:41:44 +0200 Subject: [PATCH 003/112] Add uppy companion to wopi example --- deployments/examples/ocis_wopi/.env | 5 ++++ .../examples/ocis_wopi/docker-compose.yml | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/deployments/examples/ocis_wopi/.env b/deployments/examples/ocis_wopi/.env index 2a93d76da..8ce2f94c3 100644 --- a/deployments/examples/ocis_wopi/.env +++ b/deployments/examples/ocis_wopi/.env @@ -55,3 +55,8 @@ TIKA_IMAGE= # you need uncomment following line. Please see documentation at # https://owncloud.dev/ocis/deployment/monitoring-tracing/ #COMPOSE_FILE=docker-compose.yml:monitoring_tracing/docker-compose-additions.yml + +### Uppy Companion +COMPANION_DOMAIN= +COMPANION_ONEDRIVE_KEY= +COMPANION_ONEDRIVE_SECRET= diff --git a/deployments/examples/ocis_wopi/docker-compose.yml b/deployments/examples/ocis_wopi/docker-compose.yml index f4ebf3140..8d5405049 100644 --- a/deployments/examples/ocis_wopi/docker-compose.yml +++ b/deployments/examples/ocis_wopi/docker-compose.yml @@ -11,6 +11,7 @@ services: - ${WOPISERVER_DOMAIN:-wopiserver.owncloud.test} - ${COLLABORA_DOMAIN:-collabora.owncloud.test} - ${ONLYOFFICE_DOMAIN:-onlyoffice.owncloud.test} + - ${COMPANION_DOMAIN:-companion.owncloud.test} command: - "--log.level=${TRAEFIK_LOG_LEVEL:-ERROR}" # letsencrypt configuration @@ -245,6 +246,32 @@ services: ocis-net: restart: always + companion: + image: ${COMPANION_IMAGE:-transloadit/companion:4.5.1} + networks: + ocis-net: + environment: + NODE_ENV: production + NODE_TLS_REJECT_UNAUTHORIZED: 0 + COMPANION_DATADIR: /tmp/companion/ + COMPANION_DOMAIN: ${COMPANION_DOMAIN:-companion.owncloud.test} + COMPANION_PROTOCOL: https + COMPANION_UPLOAD_URLS: ${OCIS_DOMAIN:-ocis.owncloud.test} + COMPANION_ONEDRIVE_KEY: "${COMPANION_ONEDRIVE_KEY}" + COMPANION_ONEDRIVE_SECRET: "${COMPANION_ONEDRIVE_SECRET}" + volumes: + - companion-data:/tmp/companion/ + labels: + - "traefik.enable=true" + - "traefik.http.routers.companion.entrypoints=https" + - "traefik.http.routers.companion.rule=Host(`${COMPANION_DOMAIN:-companion.owncloud.test}`)" + - "traefik.http.routers.companion.tls.certresolver=http" + - "traefik.http.routers.companion.service=companion" + - "traefik.http.services.companion.loadbalancer.server.port=3020" + logging: + driver: "local" + restart: always + inbucket: image: inbucket/inbucket networks: @@ -273,6 +300,7 @@ volumes: ocis-config: ocis-data: wopi-recovery: + companion-data: networks: ocis-net: From 3860ed442bfc3677775702a94e595bedc2f47b49 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Mon, 3 Jul 2023 20:41:46 +0200 Subject: [PATCH 004/112] Add comments to .env file --- .../ocis_wopi/daily.yml | 3 +++ .../ocis_wopi/latest.yml | 3 +++ .../ocis_wopi/released.yml | 3 +++ deployments/examples/ocis_wopi/.env | 8 ++++++-- .../ocis_wopi/config/ocis/web-daily.yaml | 18 ++++++++++++++++++ .../ocis_wopi/config/ocis/web-latest.yaml | 18 ++++++++++++++++++ .../ocis_wopi/config/ocis/web-released.yaml | 18 ++++++++++++++++++ .../examples/ocis_wopi/docker-compose.yml | 3 ++- 8 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 deployments/examples/ocis_wopi/config/ocis/web-daily.yaml create mode 100644 deployments/examples/ocis_wopi/config/ocis/web-latest.yaml create mode 100644 deployments/examples/ocis_wopi/config/ocis/web-released.yaml diff --git a/deployments/continuous-deployment-config/ocis_wopi/daily.yml b/deployments/continuous-deployment-config/ocis_wopi/daily.yml index 1ddcf9e11..85c43d21a 100644 --- a/deployments/continuous-deployment-config/ocis_wopi/daily.yml +++ b/deployments/continuous-deployment-config/ocis_wopi/daily.yml @@ -34,6 +34,9 @@ TRAEFIK_ACME_MAIL: wkloucek@owncloud.com OCIS_DOCKER_TAG: latest OCIS_DOMAIN: ocis.ocis-wopi.daily.owncloud.works + COMPANION_DOMAIN: companion.ocis-wopi.daily.owncloud.works + COMPANION_WEB_CONFIG_FILE_NAME: web-daily.yaml + COMPANION_IMAGE: owncloud/uppy-companion:3.12.13-owncloud WOPISERVER_DOMAIN: wopiserver.ocis-wopi.daily.owncloud.works COLLABORA_DOMAIN: collabora.ocis-wopi.daily.owncloud.works ONLYOFFICE_DOMAIN: onlyoffice.ocis-wopi.daily.owncloud.works diff --git a/deployments/continuous-deployment-config/ocis_wopi/latest.yml b/deployments/continuous-deployment-config/ocis_wopi/latest.yml index c98796319..a806c0a4e 100644 --- a/deployments/continuous-deployment-config/ocis_wopi/latest.yml +++ b/deployments/continuous-deployment-config/ocis_wopi/latest.yml @@ -34,6 +34,9 @@ TRAEFIK_ACME_MAIL: wkloucek@owncloud.com OCIS_DOCKER_TAG: latest OCIS_DOMAIN: ocis.ocis-wopi.latest.owncloud.works + COMPANION_DOMAIN: companion.ocis-wopi.latest.owncloud.works + COMPANION_WEB_CONFIG_FILE_NAME: web-latest.yaml + COMPANION_IMAGE: owncloud/uppy-companion:3.12.13-owncloud WOPISERVER_DOMAIN: wopiserver.ocis-wopi.latest.owncloud.works COLLABORA_DOMAIN: collabora.ocis-wopi.latest.owncloud.works ONLYOFFICE_DOMAIN: onlyoffice.ocis-wopi.latest.owncloud.works diff --git a/deployments/continuous-deployment-config/ocis_wopi/released.yml b/deployments/continuous-deployment-config/ocis_wopi/released.yml index 0e8f9355a..c8521d340 100644 --- a/deployments/continuous-deployment-config/ocis_wopi/released.yml +++ b/deployments/continuous-deployment-config/ocis_wopi/released.yml @@ -34,6 +34,9 @@ TRAEFIK_ACME_MAIL: wkloucek@owncloud.com OCIS_DOCKER_TAG: 4.0.0 OCIS_DOMAIN: ocis.ocis-wopi.released.owncloud.works + COMPANION_DOMAIN: companion.ocis-wopi.released.owncloud.works + COMPANION_IMAGE: owncloud/uppy-companion:3.12.13-owncloud + COMPANION_WEB_CONFIG_FILE_NAME: web-released.yaml WOPISERVER_DOMAIN: wopiserver.ocis-wopi.released.owncloud.works COLLABORA_DOMAIN: collabora.ocis-wopi.released.owncloud.works ONLYOFFICE_DOMAIN: onlyoffice.ocis-wopi.released.owncloud.works diff --git a/deployments/examples/ocis_wopi/.env b/deployments/examples/ocis_wopi/.env index 8ce2f94c3..2fa535523 100644 --- a/deployments/examples/ocis_wopi/.env +++ b/deployments/examples/ocis_wopi/.env @@ -47,7 +47,7 @@ ONLYOFFICE_DOMAIN= # Inbucket / Mail domain. Defaults to "mail.owncloud.test" INBUCKET_DOMAIN= -### Apache Tika Content analysis toolkit +### Apache Tika Content analysis toolkit ### # Set the desired docker image tag or digest, defaults to "latest" TIKA_IMAGE= @@ -56,7 +56,11 @@ TIKA_IMAGE= # https://owncloud.dev/ocis/deployment/monitoring-tracing/ #COMPOSE_FILE=docker-compose.yml:monitoring_tracing/docker-compose-additions.yml -### Uppy Companion +### Uppy Companion settings ### +# Domain of Uppy Companion. Defaults to "companion.owncloud.test" +COMPANION_IMAGE= COMPANION_DOMAIN= +COMPANION_WEB_CONFIG_FILE_NAME= +# Provider settings, see https://uppy.io/docs/companion/#provideroptions for reference. Empty by default, which disables providers. COMPANION_ONEDRIVE_KEY= COMPANION_ONEDRIVE_SECRET= diff --git a/deployments/examples/ocis_wopi/config/ocis/web-daily.yaml b/deployments/examples/ocis_wopi/config/ocis/web-daily.yaml new file mode 100644 index 000000000..b8890ac32 --- /dev/null +++ b/deployments/examples/ocis_wopi/config/ocis/web-daily.yaml @@ -0,0 +1,18 @@ +web: + config: + external_apps: + - id: preview + path: web-app-preview + config: + mimeTypes: + - image/tiff + - image/bmp + - image/x-ms-bmp + - id: importer + path: web-app-importer + config: + companionUrl: https://companion.ocis-wopi.daily.owncloud.works + supportedClouds: + - WebdavPublicLink + #- OneDrive # needs a client id and secret + #- GoogleDrive # needs a client id and secret and an addition to the DNS zone diff --git a/deployments/examples/ocis_wopi/config/ocis/web-latest.yaml b/deployments/examples/ocis_wopi/config/ocis/web-latest.yaml new file mode 100644 index 000000000..54c7b1b13 --- /dev/null +++ b/deployments/examples/ocis_wopi/config/ocis/web-latest.yaml @@ -0,0 +1,18 @@ +web: + config: + external_apps: + - id: preview + path: web-app-preview + config: + mimeTypes: + - image/tiff + - image/bmp + - image/x-ms-bmp + - id: importer + path: web-app-importer + config: + companionUrl: https://companion.ocis-wopi.latest.owncloud.works + supportedClouds: + - WebdavPublicLink + #- OneDrive # needs a client id and secret + #- GoogleDrive # needs a client id and secret and an addition to the DNS zone diff --git a/deployments/examples/ocis_wopi/config/ocis/web-released.yaml b/deployments/examples/ocis_wopi/config/ocis/web-released.yaml new file mode 100644 index 000000000..1a208841f --- /dev/null +++ b/deployments/examples/ocis_wopi/config/ocis/web-released.yaml @@ -0,0 +1,18 @@ +web: + config: + external_apps: + - id: preview + path: web-app-preview + config: + mimeTypes: + - image/tiff + - image/bmp + - image/x-ms-bmp + - id: importer + path: web-app-importer + config: + companionUrl: https://companion.ocis-wopi.released.owncloud.works + supportedClouds: + - WebdavPublicLink + #- OneDrive # needs a client id and secret + #- GoogleDrive # needs a client id and secret and an addition to the DNS zone diff --git a/deployments/examples/ocis_wopi/docker-compose.yml b/deployments/examples/ocis_wopi/docker-compose.yml index 8d5405049..ce8137a3e 100644 --- a/deployments/examples/ocis_wopi/docker-compose.yml +++ b/deployments/examples/ocis_wopi/docker-compose.yml @@ -90,6 +90,7 @@ services: NATS_NATS_PORT: 9233 volumes: - ./config/ocis/app-registry.yaml:/etc/ocis/app-registry.yaml + - ./config/ocis/${COMPANION_WEB_CONFIG_FILE_NAME:-web.yaml}:/etc/ocis/web.yaml - ocis-config:/etc/ocis - ocis-data:/var/lib/ocis labels: @@ -256,7 +257,7 @@ services: COMPANION_DATADIR: /tmp/companion/ COMPANION_DOMAIN: ${COMPANION_DOMAIN:-companion.owncloud.test} COMPANION_PROTOCOL: https - COMPANION_UPLOAD_URLS: ${OCIS_DOMAIN:-ocis.owncloud.test} + COMPANION_UPLOAD_URLS: "^https://${OCIS_DOMAIN:-ocis.owncloud.test}/" COMPANION_ONEDRIVE_KEY: "${COMPANION_ONEDRIVE_KEY}" COMPANION_ONEDRIVE_SECRET: "${COMPANION_ONEDRIVE_SECRET}" volumes: From c11bc458bcf93512dcc366ac11ae999717babb84 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Fri, 2 Feb 2024 11:26:53 +0100 Subject: [PATCH 005/112] Add new variable delta list between 4.0.5 and 5.0.0-rc1 Signed-off-by: Christian Richter --- docs/env-var-deltas/4.0.5-5.0.0.md | 229 +++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 docs/env-var-deltas/4.0.5-5.0.0.md diff --git a/docs/env-var-deltas/4.0.5-5.0.0.md b/docs/env-var-deltas/4.0.5-5.0.0.md new file mode 100644 index 000000000..41bcf5b87 --- /dev/null +++ b/docs/env-var-deltas/4.0.5-5.0.0.md @@ -0,0 +1,229 @@ +# Added Variables between oCIS 4.0.5 and oCIS 5.0.0-rc1 + +| File | Variable | Description | Default | +|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------| +| ocis-pkg/shared/shared_types.go | `OCIS_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. | `false` | +| services/antivirus/pkg/config/config.go | `OCIS_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| services/audit/pkg/config/config.go | `OCIS_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| services/auth-service/pkg/config/config.go | `OCIS_TRACING_ENABLED;AUTH_SERVICE_TRACING_ENABLED` | Activates tracing. | | +| | `OCIS_TRACING_TYPE;AUTH_SERVICE_TRACING_TYPE` | The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` | | +| | `OCIS_TRACING_ENDPOINT;AUTH_SERVICE_TRACING_ENDPOINT` | The endpoint of the tracing agent. | | +| | `OCIS_TRACING_COLLECTOR;AUTH_SERVICE_TRACING_COLLECTOR` | The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset. | | +| | `OCIS_LOG_LEVEL;AUTH_SERVICE_LOG_LEVEL` | The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." | | +| | `OCIS_LOG_PRETTY;AUTH_SERVICE_LOG_PRETTY` | Activates pretty log output. | | +| | `OCIS_LOG_COLOR;AUTH_SERVICE_LOG_COLOR` | Activates colorized log output. | | +| | `OCIS_LOG_FILE;AUTH_SERVICE_LOG_FILE` | The path to the log file. Activates logging to this file if set. | | +| | `AUTH_SERVICE_DEBUG_ADDR` | Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed. | | +| | `AUTH_SERVICE_DEBUG_TOKEN` | Enables pprof, which can be used for profiling. | | +| | `AUTH_SERVICE_DEBUG_ZPAGES` | Enables zpages, which can be used for collecting and viewing in-memory traces. | | +| | `AUTH_SERVICE_GRPC_ADDR` | The bind address of the GRPC service. | | +| | `AUTH_SERVICE_GRPC_PROTOCOL` | The transport protocol of the GRPC service. | | +| | `OCIS_SERVICE_ACCOUNT_ID;AUTH_SERVICE_SERVICE_ACCOUNT_ID` | The ID of the service account the service should use. See the 'auth-service' service description for more details. | | +| | `OCIS_SERVICE_ACCOUNT_SECRET;AUTH_SERVICE_SERVICE_ACCOUNT_SECRET` | The service account secret. | | +| services/auth-service/pkg/config/reva.go | `OCIS_JWT_SECRET;AUTH_SERVICE_JWT_SECRET` | The secret to mint and validate jwt tokens. | | +| services/clientlog/pkg/config/config.go | `OCIS_REVA_GATEWAY` | CS3 gateway used to look up user metadata | | +| | `OCIS_EVENTS_ENDPOINT;CLIENTLOG_EVENTS_ENDPOINT` | The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. | | +| | `OCIS_EVENTS_CLUSTER;CLIENTLOG_EVENTS_CLUSTER` | The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system. | | +| | `OCIS_INSECURE;CLIENTLOG_EVENTS_TLS_INSECURE` | Whether to verify the server TLS certificates. | | +| | `OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;CLIENTLOG_EVENTS_TLS_ROOT_CA_CERTIFICATE` | The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false. | | +| | `OCIS_EVENTS_ENABLE_TLS;CLIENTLOG_EVENTS_ENABLE_TLS` | Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.. | | +| | `OCIS_EVENTS_AUTH_USERNAME;CLIENTLOG_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;CLIENTLOG_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.. | | +| | `OCIS_JWT_SECRET;CLIENTLOG_JWT_SECRET` | The secret to mint and validate jwt tokens. | | +| | `OCIS_SERVICE_ACCOUNT_ID;CLIENTLOG_SERVICE_ACCOUNT_ID` | The ID of the service account the service should use. See the 'auth-service' service description for more details. | | +| | `OCIS_SERVICE_ACCOUNT_SECRET;CLIENTLOG_SERVICE_ACCOUNT_SECRET` | The service account secret. | | +| services/clientlog/pkg/config/debug.go | `CLIENTLOG_DEBUG_ADDR` | Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed. | | +| | `CLIENTLOG_DEBUG_TOKEN` | Token to secure the metrics endpoint. | | +| | `CLIENTLOG_DEBUG_PPROF` | Enables pprof, which can be used for profiling. | | +| | `CLIENTLOG_DEBUG_ZPAGES` | Enables zpages, which can be used for collecting and viewing in-memory traces. | | +| services/clientlog/pkg/config/log.go | `OCIS_LOG_LEVEL;CLIENTLOG_USERLOG_LOG_LEVEL` | The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." | | +| | `OCIS_LOG_PRETTY;CLIENTLOG_USERLOG_LOG_PRETTY` | Activates pretty log output. | | +| | `OCIS_LOG_COLOR;CLIENTLOG_USERLOG_LOG_COLOR` | Activates colorized log output. | | +| | `OCIS_LOG_FILE;CLIENTLOG_USERLOG_LOG_FILE` | The path to the log file. Activates logging to this file if set. | | +| services/clientlog/pkg/config/tracing.go | `OCIS_TRACING_ENABLED;CLIENTLOG_TRACING_ENABLED` | Activates tracing. | | +| | `OCIS_TRACING_TYPE;CLIENTLOG_TRACING_TYPE` | The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` | | +| | `OCIS_TRACING_ENDPOINT;CLIENTLOG_TRACING_ENDPOINT` | The endpoint of the tracing agent. | | +| | `OCIS_TRACING_COLLECTOR;CLIENTLOG_TRACING_COLLECTOR` | The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset. | | +| services/eventhistory/pkg/config/config.go | `OCIS_PERSISTENT_STORE_AUTH_USERNAME;EVENTHISTORY_STORE_AUTH_USERNAME` | The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured. | | +| | `OCIS_PERSISTENT_STORE_AUTH_PASSWORD;EVENTHISTORY_STORE_AUTH_PASSWORD` | The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured. | | +| | `OCIS_EVENTS_AUTH_USERNAME;EVENTHISTORY_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;EVENTHISTORY_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| services/frontend/pkg/config/config.go | `OCIS_ENABLE_RESHARING` | Changing this value is NOT supported. Enables the support for resharing in the clients. | | +| | `FRONTEND_DEFAULT_LINK_PERMISSIONS` | Defines the default permissions a link is being created with. Possible values are 0 (= internal link, for instance members only) and 1 (= public link with viewer permissions). Defaults to 1. | | +| | `FRONTEND_AUTO_ACCEPT_SHARES` | Defines if shares should be auto accepted by default. Users can change this setting individually in their profile. | | +| | `OCIS_CACHE_DISABLE_PERSISTENCE;FRONTEND_OCS_STAT_CACHE_DISABLE_PERSISTENCE` | Disable persistence of the cache. Only applies when using the 'nats-js-kv' store type. Defaults to false. | false | +| | `FRONTEND_OCS_LIST_OCM_SHARES` | Include OCM shares when listing shares. See the OCM service documentation for more details. | | +| | `OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_SHARE_MUST_HAVE_PASSWORD` | Set this to true if you want to enforce passwords on all public shares. | | +| | `FRONTEND_OCS_INCLUDE_OCM_SHAREES` | nclude OCM sharees when listing sharees. | | +| | `OCIS_EVENTS_ENDPOINT;FRONTEND_EVENTS_ENDPOINT` | The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. | | +| | `OCIS_EVENTS_CLUSTER;FRONTEND_EVENTS_CLUSTER` | The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system. | | +| | `OCIS_INSECURE;FRONTEND_EVENTS_TLS_INSECURE` | Whether to verify the server TLS certificates. | | +| | `FRONTEND_EVENTS_TLS_ROOT_CA_CERTIFICATE;OCS_EVENTS_TLS_ROOT_CA_CERTIFICATE` | The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false. | | +| | `OCIS_EVENTS_ENABLE_TLS;FRONTEND_EVENTS_ENABLE_TLS` | Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.. | | +| | `OCIS_EVENTS_AUTH_USERNAME;FRONTEND_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;FRONTEND_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.. | | +| | `OCIS_SERVICE_ACCOUNT_ID;FRONTEND_SERVICE_ACCOUNT_ID` | The ID of the service account the service should use. See the 'auth-service' service description for more details. | | +| | `OCIS_SERVICE_ACCOUNT_SECRET;FRONTEND_SERVICE_ACCOUNT_SECRET` | The service account secret. | | +| | `OCIS_PASSWORD_POLICY_MIN_CHARACTERS;FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS` | Define the minimum password length. Defaults to 0 if not set. | 0 | +| | `OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS;FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS` | Define the minimum number of lowercase characters. Defaults to 0 if not set. | 0 | +| | `OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS;FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS` | Define the minimum number of uppercase characters. Defaults to 0 if not set. | 0 | +| | `OCIS_PASSWORD_POLICY_MIN_DIGITS;FRONTEND_PASSWORD_POLICY_MIN_DIGITS` | Define the minimum number of digits. Defaults to 0 if not set. | 0 | +| | `OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS;FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS` | Define the minimum number of special characters. Defaults to 0 if not set. | 0 | +| | `OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST;FRONTEND_PASSWORD_POLICY_BANNED_PASSWORDS_LIST` | Path to the 'banned passwords list' file. See the documentation for more details. | | +| services/gateway/pkg/config/config.go | `GATEWAY_STORAGE_REGISTRY_DRIVER` | The driver name of the storage registry to use. | | +| | `GATEWAY_STORAGE_REGISTRY_RULES` | The rules for the storage registry. See the Environment Variable Types description for more details. | | +| | `GATEWAY_STORAGE_REGISTRY_CONFIG_JSON` | Additional configuration for the storage registry in json format. | | +| | `OCIS_CACHE_DISABLE_PERSISTENCE;GATEWAY_STAT_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the stat cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false. | false | +| | `OCIS_CACHE_DISABLE_PERSISTENCE;GATEWAY_PROVIDER_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the provider cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false. | false | +| | `OCIS_CACHE_DISABLE_PERSISTENCE;GATEWAY_CREATE_HOME_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the create home cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false. | false | +| services/graph/pkg/config/cache.go | `OCIS_CACHE_DISABLE_PERSISTENCE;GRAPH_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false. | false | +| | `OCIS_CACHE_AUTH_USERNAME;GRAPH_CACHE_AUTH_USERNAME` | The username to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured. | | +| | `OCIS_CACHE_AUTH_PASSWORD;GRAPH_CACHE_AUTH_PASSWORD` | The password to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured. | | +| services/graph/pkg/config/config.go | `GRAPH_IDENTITY_SEARCH_MIN_LENGTH` | The minimum length the search term needs to have for unprivileged users when searching for users or groups. | | +| | `OCIS_EVENTS_AUTH_USERNAME;GRAPH_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;GRAPH_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_SERVICE_ACCOUNT_ID;GRAPH_SERVICE_ACCOUNT_ID` | The ID of the service account the service should use. See the 'auth-service' service description for more details. | | +| | `OCIS_SERVICE_ACCOUNT_SECRET;GRAPH_SERVICE_ACCOUNT_SECRET` | The service account secret. | | +| | `OCIS_ENABLE_RESHARING;GRAPH_ENABLE_RESHARING` | Changing this value is NOT supported. Enables the support for resharing in the clients. | | +| services/notifications/pkg/config/config.go | `OCIS_DEFAULT_LANGUAGE` | The default language used by services and the WebUI. If not defined, English will be used as default. See the documentation for more details. | | +| | `OCIS_EVENTS_AUTH_USERNAME;NOTIFICATIONS_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;NOTIFICATIONS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_SERVICE_ACCOUNT_ID;NOTIFICATIONS_SERVICE_ACCOUNT_ID` | The ID of the service account the service should use. See the 'auth-service' service description for more details. | | +| | `OCIS_SERVICE_ACCOUNT_SECRET;NOTIFICATIONS_SERVICE_ACCOUNT_SECRET` | The service account secret. | | +| services/ocdav/pkg/config/config.go | `OCDAV_OCM_NAMESPACE` | The human readable path prefix for the ocm shares. | | +| services/ocm/pkg/config/config.go | `SHARING_OCM_PROVIDER_AUTHORIZER_DRIVER` | Driver to be used to persist ocm invites. Supported value is only 'json'. | `json` | +| | `OCM_HTTP_ADDR` | The bind address of the HTTP service. | | +| | `OCM_HTTP_PROTOCOL` | The transport protocol of the HTTP service. | | +| | `OCM_HTTP_PREFIX` | The path prefix where OCM can be accessed (defaults to /). | | +| | `OCIS_SERVICE_ACCOUNT_ID;OCM_SERVICE_ACCOUNT_ID` | The ID of the service account the service should use. See the 'auth-service' service description for more details. | | +| | `OCIS_SERVICE_ACCOUNT_SECRET;OCM_SERVICE_ACCOUNT_SECRET` | The service account secret. | | +| | `OCIS_CORS_ALLOW_ORIGINS;OCM_CORS_ALLOW_ORIGINS` | A list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin. See the Environment Variable Types description for more details. | | +| | `OCIS_CORS_ALLOW_METHODS;OCM_CORS_ALLOW_METHODS` | A list of allowed CORS methods. See following chapter for more details: *Access-Control-Allow-Methods* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods. See the Environment Variable Types description for more details. | | +| | `OCIS_CORS_ALLOW_HEADERS;OCM_CORS_ALLOW_HEADERS` | A list of allowed CORS headers. See following chapter for more details: *Access-Control-Allow-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers. See the Environment Variable Types description for more details. | | +| | `OCIS_CORS_ALLOW_CREDENTIALS;OCM_CORS_ALLOW_CREDENTIALS` | Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials. | | +| | `OCM_GRPC_ADDR` | The bind address of the GRPC service. | | +| | `OCM_GRPC_PROTOCOL` | The transport protocol of the GRPC service. | | +| | `OCM_SCIENCEMESH_PREFIX` | URL path prefix for the ScienceMesh service. Note that the string must not start with '/'. | | +| | `OCM_MESH_DIRECTORY_URL ` | URL of the mesh directory service. | | +| | `OCM_OCMD_PREFIX` | URL path prefix for the OCMd service. Note that the string must not start with '/'. | | +| | `OCM_OCMD_EXPOSE_RECIPIENT_DISPLAY_NAME` | Expose the display name of OCM share recipients. | | +| | `OCM_OCM_INVITE_MANAGER_DRIVER` | Driver to be used to persist ocm invites. Supported value is only 'json'. | `json` | +| | `OCM_OCM_INVITE_MANAGER_INSECURE` | Disable TLS certificate validation for the OCM connections. Do not set this in production environments. | | +| | `OCM_OCM_INVITE_MANAGER_JSON_FILE` | Path to the JSON file where OCM invite data will be stored. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage. | | +| | `OCM_OCM_PROVIDER_AUTHORIZER_PROVIDERS_FILE` | Path to the JSON file where ocm invite data will be stored. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage. | | +| | `OCM_OCM_PROVIDER_AUTHORIZER_VERIFY_REQUEST_HOSTNAME` | Verify the hostname of the request against the hostname of the OCM provider. | | +| | `OCM_OCM_CORE_DRIVER` | Driver to be used to persist ocm shares. Supported value is only 'json'. | `json` | +| | `OCM_OCM_STORAGE_PROVIDER_INSECURE` | Disable TLS certificate validation for the OCM connections. Do not set this in production environments. | | +| | `OCM_OCM_STORAGE_PROVIDER_STORAGE_ROOT` | Directory where the ocm storage provider persists its data like tus upload info files. | | +| | `OCM_OCM_CORE_JSON_FILE` | Path to the JSON file where OCM share data will be stored. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage. | | +| | `OCM_OCM_SHARE_PROVIDER_DRIVER` | Driver to be used to persist ocm shares. Supported value is only 'json'. | `json` | +| | `OCM_OCM_SHARE_PROVIDER_INSECURE` | Disable TLS certificate validation for the OCM connections. Do not set this in production environments. | | +| | `OCM_WEBAPP_TEMPLATE` | 'Template for the webapp url. | | +| | `OCM_OCM_SHAREPROVIDER_JSON_FILE` | Path to the JSON file where OCM share data will be stored. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage. | | +| services/ocm/pkg/config/debug.go | `OCM_DEBUG_ADDR` | Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed. | | +| | `OCM_DEBUG_TOKEN` | Token to secure the metrics endpoint. | | +| | `OCM_DEBUG_PPROF` | Enables pprof, which can be used for profiling. | | +| | `OCM_DEBUG_ZPAGES` | Enables zpages, which can be used for collecting and viewing in-memory traces. | | +| services/ocm/pkg/config/log.go | `OCIS_LOG_LEVEL;OCM_LOG_LEVEL` | The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." | | +| | `OCIS_LOG_PRETTY;OCM_LOG_PRETTY` | Activates pretty log output. | | +| | `OCIS_LOG_COLOR;OCM_LOG_COLOR` | Activates colorized log output. | | +| | `OCIS_LOG_FILE;OCM_LOG_FILE` | The path to the log file. Activates logging to this file if set. | | +| services/ocm/pkg/config/tracing.go | `OCIS_TRACING_ENABLED;OCM_TRACING_ENABLED` | Activates tracing. | | +| | `OCIS_TRACING_TYPE;OCM_TRACING_TYPE` | The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` | | +| | `OCIS_TRACING_ENDPOINT;OCM_TRACING_ENDPOINT` | The endpoint of the tracing agent. | | +| | `OCIS_TRACING_COLLECTOR;OCM_TRACING_COLLECTOR` | The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset. | | +| services/policies/pkg/config/config.go | `OCIS_EVENTS_AUTH_USERNAME;POLICIES_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;POLICIES_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| services/policies/pkg/config/tracing.go | `OCIS_TRACING_ENABLED;POLICIES_TRACING_ENABLED` | Activates tracing. | | +| | `OCIS_TRACING_TYPE;POLICIES_TRACING_TYPE` | The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` | | +| | `OCIS_TRACING_ENDPOINT;POLICIES_TRACING_ENDPOINT` | The endpoint of the tracing agent. | | +| | `OCIS_TRACING_COLLECTOR;POLICIES_TRACING_COLLECTOR` | The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset. | | +| services/postprocessing/pkg/config/config.go | `POSTPROCESSING_RETRY_BACKOFF_DURATION` | The base for the exponential backoff duration before retrying a failed postprocessing step. See the Environment Variable Types description for more details. | | +| | `POSTPROCESSING_MAX_RETRIES` | The maximum number of retries for a failed postprocessing step. | | +| | `OCIS_EVENTS_AUTH_USERNAME;POSTPROCESSING_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;POSTPROCESSING_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_PERSISTENT_STORE_AUTH_USERNAME;POSTPROCESSING_STORE_AUTH_USERNAME` | The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured. | | +| | `OCIS_PERSISTENT_STORE_AUTH_PASSWORD;POSTPROCESSING_STORE_AUTH_PASSWORD` | The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured. | | +| services/postprocessing/pkg/config/tracing.go | `OCIS_TRACING_ENABLED;POSTPROCESSING_TRACING_ENABLED` | Activates tracing. | | +| | `OCIS_TRACING_TYPE;POSTPROCESSING_TRACING_TYPE` | The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` | | +| | `OCIS_TRACING_ENDPOINT;POSTPROCESSING_TRACING_ENDPOINT` | The endpoint of the tracing agent. | | +| | `OCIS_TRACING_COLLECTOR;POSTPROCESSING_TRACING_COLLECTOR` | The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset. | | +| services/proxy/pkg/config/config.go | `OCIS_CACHE_AUTH_USERNAME;PROXY_OIDC_USERINFO_CACHE_AUTH_USERNAME` | The username to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured. | | +| | `OCIS_CACHE_AUTH_PASSWORD;PROXY_OIDC_USERINFO_CACHE_AUTH_PASSWORD` | The password to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured. | | +| | `OCIS_SERVICE_ACCOUNT_ID;PROXY_SERVICE_ACCOUNT_ID` | The ID of the service account the service should use. See the 'auth-service' service description for more details. | | +| | `OCIS_SERVICE_ACCOUNT_SECRET;PROXY_SERVICE_ACCOUNT_SECRET` | The service account secret. | | +| services/search/pkg/config/config.go | `OCIS_SERVICE_ACCOUNT_ID;SEARCH_SERVICE_ACCOUNT_ID` | The ID of the service account the service should use. See the 'auth-service' service description for more details. | | +| | `OCIS_SERVICE_ACCOUNT_SECRET;SEARCH_SERVICE_ACCOUNT_SECRET` | The service account secret. | | +| services/search/pkg/config/content.go | `SEARCH_EXTRACTOR_TIKA_CLEAN_STOP_WORDS` | Defines if stop words should be cleaned or not. See the documentation for more details. | | +| services/search/pkg/config/search.go | `OCIS_EVENTS_AUTH_USERNAME;SEARCH_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;SEARCH_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| services/settings/pkg/config/config.go | `SETTINGS_SERVICE_ACCOUNT_IDS;OCIS_SERVICE_ACCOUNT_ID` | The list of all service account IDs. These will be assigned the hidden 'service-account' role. Note: When using 'OCIS_SERVICE_ACCOUNT_ID' this will contain only one value while 'SETTINGS_SERVICE_ACCOUNT_IDS' can have multiple. See the 'auth-service' service description for more details about service accounts. | | +| | `OCIS_DEFAULT_LANGUAGE` | The default language used by services and the WebUI. If not defined, English will be used as default. See the documentation for more details. | | +| | `OCIS_CACHE_DISABLE_PERSISTENCE;SETTINGS_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false. | | +| | `OCIS_EVENTS_AUTH_USERNAME;SETTINGS_EVENTS_AUTH_USERNAME` | The username to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;SETTINGS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured. | | +| services/sharing/pkg/config/config.go | `OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD;SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD` | Set this to true if you want to enforce passwords on all public shares. | | +| | `OCIS_PASSWORD_POLICY_MIN_CHARACTERS;SHARING_PASSWORD_POLICY_MIN_CHARACTERS` | Define the minimum password length. Defaults to 0 if not set. | 0 | +| | `OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS;SHARING_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS` | Define the minimum number of lowercase characters. Defaults to 0 if not set. | 0 | +| | `OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS;SHARING_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS` | Define the minimum number of uppercase characters. Defaults to 0 if not set. | 0 | +| | `OCIS_PASSWORD_POLICY_MIN_DIGITS;SHARING_PASSWORD_POLICY_MIN_DIGITS` | Define the minimum number of digits. Defaults to 0 if not set. | 0 | +| | `OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS;SHARING_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS` | Define the minimum number of special characters. Defaults to 0 if not set. | 0 | +| | `OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST;SHARING_PASSWORD_POLICY_BANNED_PASSWORDS_LIST` | Path to the 'banned passwords list' file. See the documentation for more details. | | +| services/sse/pkg/config/config.go | `OCIS_LOG_LEVEL;SSE_LOG_LEVEL` | The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." | | +| | `OCIS_LOG_PRETTY;SSE_LOG_PRETTY` | Activates pretty log output. | | +| | `OCIS_LOG_COLOR;SSE_LOG_COLOR` | Activates colorized log output. | | +| | `OCIS_LOG_FILE;SSE_LOG_FILE` | The path to the log file. Activates logging to this file if set. | | +| | `SSE_DEBUG_ADDR` | Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed. | | +| | `SSE_DEBUG_TOKEN` | Token to secure the metrics endpoint. | | +| | `SSE_DEBUG_PPROF` | Enables pprof, which can be used for profiling. | | +| | `SSE_DEBUG_ZPAGES` | Enables zpages, which can be used for collecting and viewing in-memory traces. | | +| | `OCIS_EVENTS_ENDPOINT;SSE_EVENTS_ENDPOINT` | The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. | | +| | `OCIS_EVENTS_CLUSTER;SSE_EVENTS_CLUSTER` | The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system. | | +| | `OCIS_INSECURE;SSE_EVENTS_TLS_INSECURE` | Whether to verify the server TLS certificates. | | +| | `OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;SSE_EVENTS_TLS_ROOT_CA_CERTIFICATE` | The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false. | | +| | `OCIS_EVENTS_ENABLE_TLS;SSE_EVENTS_ENABLE_TLS` | Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.. | | +| | `OCIS_EVENTS_AUTH_USERNAME;SSE_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;SSE_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.. | | +| | `OCIS_CORS_ALLOW_ORIGINS;SSE_CORS_ALLOW_ORIGINS` | A list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin. See the Environment Variable Types description for more details. | | +| | `OCIS_CORS_ALLOW_METHODS;SSE_CORS_ALLOW_METHODS` | A list of allowed CORS methods. See following chapter for more details: *Access-Control-Allow-Methods* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods. See the Environment Variable Types description for more details. | | +| | `OCIS_CORS_ALLOW_HEADERS;SSE_CORS_ALLOW_HEADERS` | A list of allowed CORS headers. See following chapter for more details: *Access-Control-Allow-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers. See the Environment Variable Types description for more details. | | +| | `OCIS_CORS_ALLOW_CREDENTIALS;SSE_CORS_ALLOW_CREDENTIALS` | Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials. | | +| | `SSE_HTTP_ADDR` | | The bind address of the HTTP service. | | +| | `SSE_HTTP_ROOT` | | The root path of the HTTP service. | | +| | `OCIS_JWT_SECRET;SSE_JWT_SECRET` | The secret to mint and validate jwt tokens. | | +| services/sse/pkg/config/tracing.go | `OCIS_TRACING_ENABLED;SSE_TRACING_ENABLED` | Activates tracing. | | +| | `OCIS_TRACING_TYPE;SSE_TRACING_TYPE` | The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` | | +| | `OCIS_TRACING_ENDPOINT;SSE_TRACING_ENDPOINT` | The endpoint of the tracing agent. | | +| | `OCIS_TRACING_COLLECTOR;SSE_TRACING_COLLECTOR` | The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset. | | +| services/storage-system/pkg/config/config.go | `OCIS_CACHE_DISABLE_PERSISTENCE;STORAGE_SYSTEM_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false. | false | +| services/storage-users/pkg/config/config.go | `OCIS_GATEWAY_GRPC_ADDR;STORAGE_USERS_GATEWAY_GRPC_ADDR` | The bind address of the gateway GRPC address. | | +| | `OCIS_MACHINE_AUTH_API_KEY` | Machine auth API key used to validate internal requests necessary for the access to resources from other services. | | +| | `STORAGE_USERS_CLI_MAX_ATTEMPTS_RENAME_FILE` | The maximum number of attempts to rename a file when a user restores a file to an existing destination with the same name. The minimum value is 100. | | +| | `OCIS_EVENTS_AUTH_USERNAME;STORAGE_USERS_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;STORAGE_USERS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_CACHE_DISABLE_PERSISTENCE;STORAGE_USERS_STAT_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the stat cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false. | false | +| | `OCIS_CACHE_DISABLE_PERSISTENCE;STORAGE_USERS_FILEMETADATA_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the file metadata cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false. | false | +| | `OCIS_CACHE_DISABLE_PERSISTENCE;STORAGE_USERS_ID_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the id cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false. | false | +| | `OCIS_SERVICE_ACCOUNT_ID;STORAGE_USERS_SERVICE_ACCOUNT_ID` | The ID of the service account the service should use. See the 'auth-service' service description for more details. | | +| | `OCIS_SERVICE_ACCOUNT_SECRET;STORAGE_USERS_SERVICE_ACCOUNT_SECRET` | The service account secret. | | +| services/userlog/pkg/config/config.go | `OCIS_DEFAULT_LANGUAGE` | The default language used by services and the WebUI. If not defined, English will be used as default. See the documentation for more details. | | +| | `OCIS_PERSISTENT_STORE_AUTH_USERNAME;USERLOG_STORE_AUTH_USERNAME` | The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured. | | +| | `OCIS_PERSISTENT_STORE_AUTH_PASSWORD;USERLOG_STORE_AUTH_PASSWORD` | The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured. | | +| | `OCIS_EVENTS_AUTH_USERNAME;USERLOG_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_EVENTS_AUTH_PASSWORD;USERLOG_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `OCIS_SERVICE_ACCOUNT_ID;USERLOG_SERVICE_ACCOUNT_ID` | The ID of the service account the service should use. See the 'auth-service' service description for more details. | | +| | `OCIS_SERVICE_ACCOUNT_SECRET;USERLOG_SERVICE_ACCOUNT_SECRET` | The service account secret. | | +| services/web/pkg/config/options.go | `WEB_OPTION_LOGIN_URL`Specifies the target URL to the login page. This is helpful when an external IdP is used. This option is disabled by default. Example URL like: https://www.myidp.com/login. | | | +| | `WEB_OPTION_DISABLED_EXTENSIONS` | A list to disable specific Web extensions identified by their ID. The ID can e.g. be taken from the 'index.ts' file of the web extension. Example: 'com.github.owncloud.web.files.search,com.github.owncloud.web.files.print'. See the Environment Variable Types description for more details. | | +| | `WEB_OPTION_USER_LIST_REQUIRES_FILTER` | Defines whether one or more filters must be set in order to list users in the Web admin settings. Set this option to 'true' if running in an environment with a lot of users and listing all users could slow down performance. Defaults to 'false'. | false | +| | `WEB_OPTION_EMBED_ENABLED` | Defines whether Web should be running in 'embed' mode. Setting this to 'true' will enable a stripped down version of Web with reduced functionality used to integrate Web into other applications like via iFrame. Setting it to 'false' or not setting it (default) will run Web as usual with all functionality enabled. See the text description for more details. | | +| | `WEB_OPTION_EMBED_TARGET` | Defines how Web is being integrated when running in 'embed' mode. Currently, the only supported options are '' (empty) and 'location'. With '' which is the default, Web will run regular as defined via the 'embed.enabled' config option. With 'location', Web will run embedded as location picker. Resource selection will be disabled and the selected resources array always includes the current folder as the only item. See the text description for more details. | | +| | `WEB_OPTION_EMBED_MESSAGES_ORIGIN` | Defines a URL under which Web can be integrated via iFrame in 'embed' mode. Note that setting this is mandatory when running Web in 'embed' mode. Use '*' as value to allow running the iFrame under any URL, although this is not recommended for security reasons. See the text description for more details. | | +| | `WEB_OPTION_EMBED_DELEGATE_AUTHENTICATION` | Defines whether Web should require authentication to be done by the parent application when running in 'embed' mode. If set to 'true' Web will not try to authenticate the user on its own but will require an access token coming from the parent application. Defaults to being unset. | | +| | `WEB_OPTION_EMBED_DELEGATE_AUTHENTICATION_ORIGIN` | Defines the host to validate the message event origin against when running Web in 'embed' mode with delegated authentication. Defaults to event message origin validation being omitted, which is only recommended for development setups. | | +| | `WEB_OPTION_CONCURRENT_REQUESTS_RESOURCE_BATCH_ACTIONS` | Defines the maximum number of concurrent requests per file/folder/space batch action. Defaults to 4. | | +| | `WEB_OPTION_CONCURRENT_REQUESTS_SSE` | Defines the maximum number of concurrent requests in SSE event handlers. Defaults to 4. | | +| | `WEB_OPTION_CONCURRENT_REQUESTS_SHARES_CREATE` | Defines the maximum number of concurrent requests per sharing invite batch. Defaults to 4. | | +| | `WEB_OPTION_CONCURRENT_REQUESTS_SHARES_LIST` | Defines the maximum number of concurrent requests when loading individual share information inside listings. Defaults to 2. | | From 4bea4357a9a722d872cfd435d7d44512a68f25de Mon Sep 17 00:00:00 2001 From: nirajacharya2 <122071597+nirajacharya2@users.noreply.github.com> Date: Fri, 2 Feb 2024 17:50:41 +0545 Subject: [PATCH 006/112] Added test for sharingNg for endpoint sharedByMe (#8311) Signed-off-by: sagargurung1001@gmail.com Added tests for list shared by me Signed-off-by: sagargurung1001@gmail.com adding test for updating permission of a share Co-authored-by: sagargurung1001@gmail.com --- .../features/apiSharingNg/sharedByMe.feature | 71 +++++++++++++++++++ .../features/bootstrap/SharingNgContext.php | 49 ++++++++++--- 2 files changed, 111 insertions(+), 9 deletions(-) diff --git a/tests/acceptance/features/apiSharingNg/sharedByMe.feature b/tests/acceptance/features/apiSharingNg/sharedByMe.feature index b2e368917..ecdd5a34e 100644 --- a/tests/acceptance/features/apiSharingNg/sharedByMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedByMe.feature @@ -1463,3 +1463,74 @@ Feature: resources shared by user } } """ + + + Scenario: user updates expiration date of a share + Given user "Alice" has uploaded file with content "hello world" to "testfile.txt" + And user "Alice" has sent the following share invitation: + | resource | testfile.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + When user "Alice" updates the last share with the following using the Graph API: + | space | Personal | + | resource | testfile.txt | + | expirationDateTime | 2200-07-15T14:00:00Z | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "expirationDateTime", + "grantedToV2", + "id", + "roles" + ], + "properties": { + "expirationDateTime": { + "type": "string", + "enum": ["2200-07-15T14:00:00Z"] + }, + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties":{ + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + """ \ No newline at end of file diff --git a/tests/acceptance/features/bootstrap/SharingNgContext.php b/tests/acceptance/features/bootstrap/SharingNgContext.php index 69cbec4a7..da74b9560 100644 --- a/tests/acceptance/features/bootstrap/SharingNgContext.php +++ b/tests/acceptance/features/bootstrap/SharingNgContext.php @@ -231,6 +231,26 @@ class SharingNgContext implements Context { ); } + /** + * @When user :user updates the last share with the following using the Graph API: + * + * @param string $user + * @param TableNode $table + * + * @return void + */ + public function userUpdatesTheLastShareWithFollowingUsingGraphApi($user, TableNode $table) { + $response = $this->featureContext->shareNgGetLastCreatedUserGroupShare(); + $permissionID = json_decode($response->getBody()->getContents())->value[0]->id; + $this->featureContext->setResponse( + $this->updateShare( + $user, + $table, + $permissionID + ) + ); + } + /** * @When user :user sends the following share invitation with file-id :fileId using the Graph API: * @@ -287,16 +307,28 @@ class SharingNgContext implements Context { * @throws Exception */ public function userUpdatesLastPublicLinkShareUsingTheGraphApiWith(string $user, TableNode $body):void { + $this->featureContext->setResponse( + $this->updateShare( + $user, + $body, + $this->featureContext->shareNgGetLastCreatedLinkShareID() + ) + ); + } + + /** + * @param string $user + * @param TableNode $body + * @param string $permissionID + * + * @return ResponseInterface + */ + public function updateShare(string $user, TableNode $body, string $permissionID): ResponseInterface { $bodyRows = $body->getRowsHash(); $space = $bodyRows['space']; - $resourceType = $bodyRows['resourceType']; $resource = $bodyRows['resource']; $spaceId = ($this->spacesContext->getSpaceByName($user, $space))["id"]; - if ($resourceType === 'folder') { - $itemId = $this->spacesContext->getResourceId($user, $space, $resource); - } else { - $itemId = $this->spacesContext->getFileId($user, $space, $resource); - } + $itemId = $this->spacesContext->getResourceId($user, $space, $resource); if (\array_key_exists('role', $bodyRows) && \array_key_exists('expirationDateTime', $bodyRows)) { $body = [ @@ -319,7 +351,7 @@ class SharingNgContext implements Context { throw new Error('Expiration date or role is missing to update for share link!'); } - $response = GraphHelper::updateLinkShare( + return GraphHelper::updateLinkShare( $this->featureContext->getBaseUrl(), $this->featureContext->getStepLineRef(), $user, @@ -327,9 +359,8 @@ class SharingNgContext implements Context { $spaceId, $itemId, \json_encode($body), - $this->featureContext->shareNgGetLastCreatedLinkShareID() + $permissionID ); - $this->featureContext->setResponse($response); } /** From 4d5490a60135df8beb8a64626192e6555858f74e Mon Sep 17 00:00:00 2001 From: ownClouders Date: Sat, 3 Feb 2024 00:37:35 +0000 Subject: [PATCH 007/112] [tx] updated from transifex --- .../pkg/email/l10n/locale/tr/LC_MESSAGES/notifications.po | 2 +- .../pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po | 2 +- .../userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po | 2 +- .../pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po | 2 +- .../userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po | 2 +- .../pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/notifications/pkg/email/l10n/locale/tr/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/tr/LC_MESSAGES/notifications.po index 4fee69de5..537615b17 100644 --- a/services/notifications/pkg/email/l10n/locale/tr/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/tr/LC_MESSAGES/notifications.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-14 00:06+0000\n" +"POT-Creation-Date: 2024-02-03 00:35+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Begüm Topyıldız , 2023\n" "Language-Team: Turkish (https://app.transifex.com/owncloud-org/teams/6149/tr/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po index 167cc071a..452d8bfa1 100644 --- a/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-14 00:06+0000\n" +"POT-Creation-Date: 2024-02-03 00:35+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Martin Zhekov , 2023\n" "Language-Team: Bulgarian (Bulgaria) (https://app.transifex.com/owncloud-org/teams/6149/bg_BG/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po index 65147657b..8402e0077 100644 --- a/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-14 00:06+0000\n" +"POT-Creation-Date: 2024-02-03 00:35+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Hugo Blackwood, 2023\n" "Language-Team: Korean (https://app.transifex.com/owncloud-org/teams/6149/ko/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po index 1ad4ac3c0..f5a140dd4 100644 --- a/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-14 00:06+0000\n" +"POT-Creation-Date: 2024-02-03 00:35+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Flávio Veras , 2023\n" "Language-Team: Portuguese (Brazil) (https://app.transifex.com/owncloud-org/teams/6149/pt_BR/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po index 5347f0216..dc4cbe4b1 100644 --- a/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-14 00:06+0000\n" +"POT-Creation-Date: 2024-02-03 00:35+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Besnik Bleta , 2023\n" "Language-Team: Albanian (https://app.transifex.com/owncloud-org/teams/6149/sq/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po index 3d9b4fd62..fc79791ed 100644 --- a/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-14 00:06+0000\n" +"POT-Creation-Date: 2024-02-03 00:35+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Shouyuan, 2023\n" "Language-Team: Chinese (China) (https://app.transifex.com/owncloud-org/teams/6149/zh_CN/)\n" From 704b372205af11887f5cba3eb66e4fcd85bdc838 Mon Sep 17 00:00:00 2001 From: Sawjan Gurung Date: Mon, 5 Feb 2024 11:43:19 +0545 Subject: [PATCH 008/112] [tests-only] extend share invite json response assertion (#8348) * rename %eTag% to %etag_pattern% for clarity * check more response properties --- .../apiSharingNg/sharedWithMe.feature | 449 ++++++++++-------- .../features/bootstrap/FeatureContext.php | 50 +- 2 files changed, 280 insertions(+), 219 deletions(-) diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature index 7078597f7..82b8a2ee0 100644 --- a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature @@ -35,6 +35,8 @@ Feature: an user gets the resources shared to them "items": { "type": "object", "required": [ + "@UI.Hidden", + "@client.synchronize", "createdBy", "eTag", "file", @@ -42,9 +44,18 @@ Feature: an user gets the resources shared to them "lastModifiedDateTime", "name", "parentReference", - "remoteItem" + "remoteItem", + "size" ], "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [true] + }, "createdBy": { "type": "object", "required": [ @@ -69,7 +80,7 @@ Feature: an user gets the resources shared to them }, "eTag": { "type": "string", - "pattern": "%eTag%" + "pattern": "%etag_pattern%" }, "file": { "type": "object", @@ -95,16 +106,21 @@ Feature: an user gets the resources shared to them "type": "object", "required": [ "driveId", - "driveType" + "driveType", + "id" ], "properties": { "driveId": { "type": "string", "pattern": "^%space_id_pattern%$" }, - "driveType" : { + "driveType": { "type": "string", "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" } } }, @@ -151,7 +167,7 @@ Feature: an user gets the resources shared to them }, "eTag": { "type": "string", - "pattern": "%eTag%" + "pattern": "%etag_pattern%" }, "file": { "type": "object", @@ -175,107 +191,121 @@ Feature: an user gets the resources shared to them "textfile0.txt" ] }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["personal"] + } + } + }, "permissions": { "type": "array", "items": [ { "type": "object", "required": [ - "grantedToV2", - "id", - "invitation" - ], - "properties": { - "id": { - "type": "string" - }, - "grantedToV2": { - "type": "object", - "required": [ - "user" - ], - "properties": { - "user": { - "type": "object", - "properties": { - "displayName": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": [ - "displayName", - "id" - ] - } - } - }, - "invitation": { - "type": "object", - "properties": { - "invitedBy": { - "type": "object", - "properties": { - "user": { - "type": "object", - "properties": { - "displayName": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": [ - "displayName", - "id" - ] - } - }, - "required": [ - "user" - ] - } - }, - "required": [ - "invitedBy" - ] - }, - "@libre.graph.permissions.actions": { - "type": "array", - "items": [ - { - "type": "string" - } - ] - }, - "roles": { - "type": "array", - "items": [ - { - "type": "string" - } - ] - } - } - } - ] - }, - "size": { - "type": "number", - "enum": [ - 11 - ] - } - } - } - } - } - } - } - } + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "invitation": { + "type": "object", + "properties": { + "invitedBy": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + }, + "required": [ + "displayName", + "id" + ] + } + }, + "required": [ + "user" + ] + } + }, + "required": [ + "invitedBy" + ] + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + ] + } + } + }, + "size": { + "type": "number", + "enum": [11] + } + } + } + } + } + } """ @@ -302,6 +332,8 @@ Feature: an user gets the resources shared to them "items": { "type": "object", "required": [ + "@UI.Hidden", + "@client.synchronize", "createdBy", "eTag", "folder", @@ -312,6 +344,14 @@ Feature: an user gets the resources shared to them "remoteItem" ], "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [true] + }, "createdBy": { "type": "object", "required": [ @@ -336,7 +376,7 @@ Feature: an user gets the resources shared to them }, "eTag": { "type": "string", - "pattern": "%eTag%" + "pattern": "%etag_pattern%" }, "folder": { "type": "object", @@ -357,16 +397,21 @@ Feature: an user gets the resources shared to them "type": "object", "required": [ "driveId", - "driveType" + "driveType", + "id" ], "properties": { "driveId": { "type": "string", "pattern": "^%space_id_pattern%$" }, - "driveType" : { + "driveType": { "type": "string", "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" } } }, @@ -412,7 +457,7 @@ Feature: an user gets the resources shared to them }, "eTag": { "type": "string", - "pattern": "%eTag%" + "pattern": "%etag_pattern%" }, "file": { "type": "object", @@ -422,7 +467,7 @@ Feature: an user gets the resources shared to them "properties": { "mimeType": { "type": "string", - "pattern": "text/plain" + "enum": ["text/plain"] } } }, @@ -436,99 +481,115 @@ Feature: an user gets the resources shared to them "folder" ] }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["personal"] + } + } + }, "permissions": { "type": "array", "items": [ { "type": "object", "required": [ - "grantedToV2", - "id", - "invitation" - ], - "properties": { - "id": { - "type": "string" - }, - "grantedToV2": { - "type": "object", - "required": [ - "user" - ], - "properties": { - "user": { - "type": "object", - "properties": { - "displayName": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": [ - "displayName", - "id" - ] - } - } - }, - "invitation": { - "type": "object", - "properties": { - "invitedBy": { - "type": "object", - "properties": { - "user": { - "type": "object", - "properties": { - "displayName": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": [ - "displayName", - "id" - ] - } - }, - "required": [ - "user" - ] - } - }, - "required": [ - "invitedBy" - ] - }, - "@libre.graph.permissions.actions": { - "type": "array", - "items": [ - { - "type": "string" - } - ] - }, - "roles": { - "type": "array", - "items": [ - { - "type": "string" - } - ] - } - } - } - ] - } - } - } - } - } - } - } - } + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + }, + "required": [ + "displayName", + "id" + ] + } + } + }, + "invitation": { + "type": "object", + "properties": { + "invitedBy": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + }, + "required": [ + "displayName", + "id" + ] + } + }, + "required": [ + "user" + ] + } + }, + "required": [ + "invitedBy" + ] + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + ] + } + } + } + } + } + } + } + } """ diff --git a/tests/acceptance/features/bootstrap/FeatureContext.php b/tests/acceptance/features/bootstrap/FeatureContext.php index 53571a961..1b7422d5f 100644 --- a/tests/acceptance/features/bootstrap/FeatureContext.php +++ b/tests/acceptance/features/bootstrap/FeatureContext.php @@ -2746,30 +2746,6 @@ class FeatureContext extends BehatVariablesContext { ], "parameter" => [] ], - [ - "code" => "%group_id_pattern%", - "function" => [ - __NAMESPACE__ . '\TestHelpers\GraphHelper', - "getUUIDv4Regex" - ], - "parameter" => [] - ], - [ - "code" => "%space_id_pattern%", - "function" => [ - __NAMESPACE__ . '\TestHelpers\GraphHelper', - "getSpaceIdRegex" - ], - "parameter" => [] - ], - [ - "code" => "%user_id_pattern%", - "function" => [ - __NAMESPACE__ . '\TestHelpers\GraphHelper', - "getUUIDv4Regex" - ], - "parameter" => [] - ], [ "code" => "%user_id%", "function" => [ @@ -2784,6 +2760,22 @@ class FeatureContext extends BehatVariablesContext { ], "parameter" => [$group] ], + [ + "code" => "%user_id_pattern%", + "function" => [ + __NAMESPACE__ . '\TestHelpers\GraphHelper', + "getUUIDv4Regex" + ], + "parameter" => [] + ], + [ + "code" => "%group_id_pattern%", + "function" => [ + __NAMESPACE__ . '\TestHelpers\GraphHelper', + "getUUIDv4Regex" + ], + "parameter" => [] + ], [ "code" => "%role_id_pattern%", "function" => [ @@ -2808,6 +2800,14 @@ class FeatureContext extends BehatVariablesContext { ], "parameter" => [] ], + [ + "code" => "%space_id_pattern%", + "function" => [ + __NAMESPACE__ . '\TestHelpers\GraphHelper', + "getSpaceIdRegex" + ], + "parameter" => [] + ], [ "code" => "%share_id_pattern%", "function" => [ @@ -2817,7 +2817,7 @@ class FeatureContext extends BehatVariablesContext { "parameter" => [] ], [ - "code" => "%eTag%", + "code" => "%etag_pattern%", "function" => [ __NAMESPACE__ . '\TestHelpers\GraphHelper', "getEtagRegex" From 82e6e742ff3d141869fe0a0ed381ddc87f6fca3f Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Thu, 1 Feb 2024 14:22:55 +0100 Subject: [PATCH 009/112] Bump reva Signed-off-by: Christian Richter --- go.mod | 2 + go.sum | 4 +- .../http/services/owncloud/ocdav/context.go | 18 +++++++ .../http/services/owncloud/ocdav/dav.go | 6 +-- .../services/owncloud/ocdav/errors/error.go | 2 + .../services/owncloud/ocdav/publicfile.go | 25 ++++++---- .../http/services/owncloud/ocdav/tus.go | 47 +++++++++++++++++++ vendor/modules.txt | 3 +- 8 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/context.go diff --git a/go.mod b/go.mod index c14c2fb16..ffb34de5b 100644 --- a/go.mod +++ b/go.mod @@ -361,3 +361,5 @@ replace github.com/egirna/icap-client => github.com/fschade/icap-client v0.0.0-2 // exclude the v2 line of go-sqlite3 which was released accidentally and prevents pulling in newer versions of go-sqlite3 // see https://github.com/mattn/go-sqlite3/issues/965 for more details exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible + +replace github.com/cs3org/reva/v2 => github.com/dragonchaser/reva/v2 v2.4.1-0.20240201130351-4b087c2a6b7b diff --git a/go.sum b/go.sum index 3446ba9ad..7d539581e 100644 --- a/go.sum +++ b/go.sum @@ -1019,8 +1019,6 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= -github.com/cs3org/reva/v2 v2.18.1-0.20240129131717-cff0a2eeb959 h1:8uiYRWlbrhQJk4pHawpJUTFx/Yy0G5yvMYam2TMKDYo= -github.com/cs3org/reva/v2 v2.18.1-0.20240129131717-cff0a2eeb959/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= @@ -1057,6 +1055,8 @@ github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dragonchaser/reva/v2 v2.4.1-0.20240201130351-4b087c2a6b7b h1:ld5hcVkNivZWgzV2CnMsp1rdRE3ktnscT7PHx6IHKDk= +github.com/dragonchaser/reva/v2 v2.4.1-0.20240201130351-4b087c2a6b7b/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/context.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/context.go new file mode 100644 index 000000000..ed48e6c32 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/context.go @@ -0,0 +1,18 @@ +package ocdav + +import ( + "context" + + cs3storage "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +type tokenStatInfoKey struct{} + +func ContextWithTokenStatInfo(ctx context.Context, info *cs3storage.ResourceInfo) context.Context { + return context.WithValue(ctx, tokenStatInfoKey{}, info) +} + +func TokenStatInfoFromContext(ctx context.Context) (*cs3storage.ResourceInfo, bool) { + v, ok := ctx.Value(tokenStatInfoKey{}).(*cs3storage.ResourceInfo) + return v, ok +} diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/dav.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/dav.go index 93e12da1d..db26d729b 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/dav.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/dav.go @@ -44,8 +44,6 @@ const ( _trashbinPath = "trash-bin" ) -type tokenStatInfoKey struct{} - // DavHandler routes to the different sub handlers type DavHandler struct { AvatarsHandler *AvatarsHandler @@ -318,9 +316,9 @@ func (h *DavHandler) Handler(s *svc) http.Handler { } log.Debug().Interface("statInfo", sRes.Info).Msg("Stat info from public link token path") + ctx := ContextWithTokenStatInfo(ctx, sRes.Info) + r = r.WithContext(ctx) if sRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER { - ctx := context.WithValue(ctx, tokenStatInfoKey{}, sRes.Info) - r = r.WithContext(ctx) h.PublicFileHandler.Handler(s).ServeHTTP(w, r) } else { h.PublicFolderHandler.Handler(s).ServeHTTP(w, r) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors/error.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors/error.go index b573dd20a..78df1d198 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors/error.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors/error.go @@ -159,6 +159,8 @@ var ( ErrNoSuchLock = errors.New("webdav: no such lock") // ErrNotImplemented is returned when hitting not implemented code paths ErrNotImplemented = errors.New("webdav: not implemented") + // ErrTokenNotFound is returned when a token is not found + ErrTokenStatInfoMissing = errors.New("webdav: token stat info missing") ) // HandleErrorStatus checks the status code, logs a Debug or Error level message diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/publicfile.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/publicfile.go index 31c92616b..91bfe5f1b 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/publicfile.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/publicfile.go @@ -26,7 +26,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" + ocdaverrors "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind" "github.com/cs3org/reva/v2/pkg/appctx" @@ -96,7 +96,16 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "token_propfind") defer span.End() - tokenStatInfo := ctx.Value(tokenStatInfoKey{}).(*provider.ResourceInfo) + tokenStatInfo, ok := TokenStatInfoFromContext(ctx) + if !ok { + span.RecordError(ocdaverrors.ErrTokenStatInfoMissing) + span.SetStatus(codes.Error, ocdaverrors.ErrTokenStatInfoMissing.Error()) + span.SetAttributes(semconv.HTTPStatusCodeKey.Int(http.StatusInternalServerError)) + w.WriteHeader(http.StatusInternalServerError) + b, err := ocdaverrors.Marshal(http.StatusInternalServerError, ocdaverrors.ErrTokenStatInfoMissing.Error(), "") + ocdaverrors.HandleWebdavError(appctx.GetLogger(ctx), w, b, err) + return + } sublog := appctx.GetLogger(ctx).With().Interface("tokenStatInfo", tokenStatInfo).Logger() sublog.Debug().Msg("handlePropfindOnToken") @@ -109,20 +118,20 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s sublog.Debug().Str("depth", dh).Msg(err.Error()) w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Invalid Depth header value: %v", dh) - b, err := errors.Marshal(http.StatusBadRequest, m, "") - errors.HandleWebdavError(&sublog, w, b, err) + b, err := ocdaverrors.Marshal(http.StatusBadRequest, m, "") + ocdaverrors.HandleWebdavError(&sublog, w, b, err) return } if depth == net.DepthInfinity && !s.c.AllowPropfindDepthInfinitiy { - span.RecordError(errors.ErrInvalidDepth) + span.RecordError(ocdaverrors.ErrInvalidDepth) span.SetStatus(codes.Error, "DEPTH: infinity is not supported") span.SetAttributes(semconv.HTTPStatusCodeKey.Int(http.StatusBadRequest)) - sublog.Debug().Str("depth", dh).Msg(errors.ErrInvalidDepth.Error()) + sublog.Debug().Str("depth", dh).Msg(ocdaverrors.ErrInvalidDepth.Error()) w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Invalid Depth header value: %v", dh) - b, err := errors.Marshal(http.StatusBadRequest, m, "") - errors.HandleWebdavError(&sublog, w, b, err) + b, err := ocdaverrors.Marshal(http.StatusBadRequest, m, "") + ocdaverrors.HandleWebdavError(&sublog, w, b, err) return } diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go index c32f4022a..95ba36aa8 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go @@ -24,6 +24,7 @@ import ( "io" "net/http" "path" + "path/filepath" "strconv" "strings" "time" @@ -115,6 +116,15 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. w.WriteHeader(http.StatusPreconditionFailed) return } + + // Test if the target is a secret filedrop + var isSecretFileDrop bool + tokenStatInfo, ok := TokenStatInfoFromContext(ctx) + // We assume that when the uploader can create containers, but is not allowed to list them, it is a secret file drop + if ok && tokenStatInfo.GetPermissionSet().CreateContainer && !tokenStatInfo.GetPermissionSet().ListContainer { + isSecretFileDrop = true + } + // r.Header.Get(net.HeaderOCChecksum) // TODO must be SHA1, ADLER32 or MD5 ... in capital letters???? // curl -X PUT https://demo.owncloud.com/remote.php/webdav/testcs.bin -u demo:demo -d '123' -v -H 'OC-Checksum: SHA1:40bd001563085fc35165329ea1ff5c5ecbdbbeef' @@ -158,6 +168,43 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. return } } + if isSecretFileDrop { + lReq := &provider.ListContainerRequest{ + Ref: &provider.Reference{ + ResourceId: sRes.GetInfo().GetParentId(), + }, + } + lRes, err := client.ListContainer(ctx, lReq) + if err != nil { + log.Error().Err(err).Msg("error sending grpc stat request") + w.WriteHeader(http.StatusInternalServerError) + return + } + if lRes.Status.Code != rpc.Code_CODE_OK { + log.Debug().Err(err).Msg("error listing container") + errors.HandleErrorStatus(&log, w, lRes.Status) + return + } + // iterate over the listing to determine next suffix + var itemMap = make(map[string]struct{}) + for _, fi := range lRes.Infos { + itemMap[fi.GetName()] = struct{}{} + } + ext := filepath.Ext(sRes.GetInfo().GetName()) + fileName := strings.TrimSuffix(sRes.GetInfo().GetName(), ext) + if strings.HasSuffix(fileName, ".tar") { + fileName = strings.TrimSuffix(fileName, ".tar") + ext = filepath.Ext(fileName) + "." + ext + } + // starts with two because "normal" humans begin counting with 1 and we say the existing file is the first one + for i := 2; i < len(itemMap)+3; i++ { + if _, ok := itemMap[fileName+" ("+strconv.Itoa(i)+")"+ext]; !ok { + sRes.GetInfo().Name = fileName + " (" + strconv.Itoa(i) + ")" + ext + ref.Path = filepath.Join(filepath.Dir(ref.GetPath()), sRes.GetInfo().GetName()) + break + } + } + } } uploadLength, err := strconv.ParseInt(r.Header.Get(net.HeaderUploadLength), 10, 64) diff --git a/vendor/modules.txt b/vendor/modules.txt index 79cd2bad9..fda9b7ba9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -362,7 +362,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1 github.com/cs3org/go-cs3apis/cs3/types/v1beta1 -# github.com/cs3org/reva/v2 v2.18.1-0.20240129131717-cff0a2eeb959 +# github.com/cs3org/reva/v2 v2.18.1-0.20240129131717-cff0a2eeb959 => github.com/dragonchaser/reva/v2 v2.4.1-0.20240201130351-4b087c2a6b7b ## explicit; go 1.21 github.com/cs3org/reva/v2/cmd/revad/internal/grace github.com/cs3org/reva/v2/cmd/revad/runtime @@ -2327,3 +2327,4 @@ stash.kopano.io/kgol/rndm # github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/kobergj/plugins/v4/store/nats-js-kv v0.0.0-20231207143248-4d424e3ae348 # github.com/studio-b12/gowebdav => github.com/aduffeck/gowebdav v0.0.0-20231215102054-212d4a4374f6 # github.com/egirna/icap-client => github.com/fschade/icap-client v0.0.0-20240123094924-5af178158eaf +# github.com/cs3org/reva/v2 => github.com/dragonchaser/reva/v2 v2.4.1-0.20240201130351-4b087c2a6b7b From 0a420dd33300968cce2478c532d2a555a8912365 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Fri, 2 Feb 2024 09:45:02 +0100 Subject: [PATCH 010/112] remove expected failure Signed-off-by: Christian Richter --- tests/acceptance/expected-failures-API-on-OCIS-storage.md | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/acceptance/expected-failures-API-on-OCIS-storage.md b/tests/acceptance/expected-failures-API-on-OCIS-storage.md index f53753854..b66665498 100644 --- a/tests/acceptance/expected-failures-API-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-API-on-OCIS-storage.md @@ -118,7 +118,6 @@ cannot share a folder with create permission #### [Upload-only shares must not overwrite but create a separate file](https://github.com/owncloud/ocis/issues/1267) - [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:13](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L13) -- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:121](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L121) #### [d:quota-available-bytes in dprop of PROPFIND give wrong response value](https://github.com/owncloud/ocis/issues/8197) From 13b01f0e0c4aafb2c9b2383d91f0ca8dde489c73 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Fri, 2 Feb 2024 09:47:17 +0100 Subject: [PATCH 011/112] bump reva Signed-off-by: Christian Richter --- .../bump-reva-for-fileincrementor.md | 6 + go.mod | 4 +- go.sum | 4 +- .../http/services/owncloud/ocdav/context.go | 2 + .../http/services/owncloud/ocdav/filedrop.go | 47 ++ .../http/services/owncloud/ocdav/put.go | 41 + .../http/services/owncloud/ocdav/tus.go | 35 +- .../v2/pkg/publicshare/manager/json/json.go | 53 +- .../manager/json/persistence/file/file.go | 23 +- .../reva/v2/pkg/storage/fs/loader/loader.go | 1 + .../storage/fs/posix/blobstore/blobstore.go | 122 +++ .../v2/pkg/storage/fs/posix/lookup/lookup.go | 375 ++++++++ .../reva/v2/pkg/storage/fs/posix/posix.go | 100 +++ .../reva/v2/pkg/storage/fs/posix/tree/tree.go | 799 ++++++++++++++++++ .../utils/decomposedfs/aspects/aspects.go | 33 + .../utils/decomposedfs/decomposedfs.go | 61 +- .../utils/decomposedfs/lookup/lookup.go | 30 +- .../utils/decomposedfs/migrator/migrator.go | 6 +- .../storage/utils/decomposedfs/node/node.go | 53 +- .../{ => permissions}/spacepermissions.go | 20 +- .../pkg/storage/utils/decomposedfs/spaces.go | 22 +- .../decomposedfs/tree/propagator/async.go | 4 +- .../tree/propagator/propagator.go | 5 +- .../decomposedfs/tree/propagator/sync.go | 5 +- .../storage/utils/decomposedfs/tree/tree.go | 4 +- .../utils/decomposedfs/upload/store.go | 4 +- vendor/modules.txt | 9 +- 27 files changed, 1729 insertions(+), 139 deletions(-) create mode 100644 changelog/unreleased/bump-reva-for-fileincrementor.md create mode 100644 vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/filedrop.go create mode 100644 vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go create mode 100644 vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/lookup/lookup.go create mode 100644 vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/posix.go create mode 100644 vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/tree/tree.go create mode 100644 vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects/aspects.go rename vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/{ => permissions}/spacepermissions.go (92%) diff --git a/changelog/unreleased/bump-reva-for-fileincrementor.md b/changelog/unreleased/bump-reva-for-fileincrementor.md new file mode 100644 index 000000000..77ea4e34c --- /dev/null +++ b/changelog/unreleased/bump-reva-for-fileincrementor.md @@ -0,0 +1,6 @@ +Enhancement: bump reva + +We have bumped reve to pull in the changes needed for automatically increment filenames on upload collisions in secret filedrops. + +https://github.com/owncloud/ocis/pull/8340 +https://github.com/owncloud/ocis/issues/8291 diff --git a/go.mod b/go.mod index ffb34de5b..310ea735f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/blevesearch/bleve/v2 v2.3.10 github.com/coreos/go-oidc/v3 v3.9.0 github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 - github.com/cs3org/reva/v2 v2.18.1-0.20240129131717-cff0a2eeb959 + github.com/cs3org/reva/v2 v2.18.1-0.20240205065033-2c21ada2ae52 github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 github.com/disintegration/imaging v1.6.2 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e @@ -361,5 +361,3 @@ replace github.com/egirna/icap-client => github.com/fschade/icap-client v0.0.0-2 // exclude the v2 line of go-sqlite3 which was released accidentally and prevents pulling in newer versions of go-sqlite3 // see https://github.com/mattn/go-sqlite3/issues/965 for more details exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible - -replace github.com/cs3org/reva/v2 => github.com/dragonchaser/reva/v2 v2.4.1-0.20240201130351-4b087c2a6b7b diff --git a/go.sum b/go.sum index 7d539581e..7c193d7f3 100644 --- a/go.sum +++ b/go.sum @@ -1019,6 +1019,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/reva/v2 v2.18.1-0.20240205065033-2c21ada2ae52 h1:Jeh8q6WKl4gcK7GMayn56y1uxbw91XvyuZcvY7SiDRk= +github.com/cs3org/reva/v2 v2.18.1-0.20240205065033-2c21ada2ae52/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= @@ -1055,8 +1057,6 @@ github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dragonchaser/reva/v2 v2.4.1-0.20240201130351-4b087c2a6b7b h1:ld5hcVkNivZWgzV2CnMsp1rdRE3ktnscT7PHx6IHKDk= -github.com/dragonchaser/reva/v2 v2.4.1-0.20240201130351-4b087c2a6b7b/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/context.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/context.go index ed48e6c32..7c0a6d443 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/context.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/context.go @@ -8,10 +8,12 @@ import ( type tokenStatInfoKey struct{} +// ContextWithTokenStatInfo adds the token stat info to the context func ContextWithTokenStatInfo(ctx context.Context, info *cs3storage.ResourceInfo) context.Context { return context.WithValue(ctx, tokenStatInfoKey{}, info) } +// TokenStatInfoFromContext returns the token stat info from the context func TokenStatInfoFromContext(ctx context.Context) (*cs3storage.ResourceInfo, bool) { v, ok := ctx.Value(tokenStatInfoKey{}).(*cs3storage.ResourceInfo) return v, ok diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/filedrop.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/filedrop.go new file mode 100644 index 000000000..53cb41799 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/filedrop.go @@ -0,0 +1,47 @@ +package ocdav + +import ( + "context" + "errors" + "path/filepath" + "strconv" + "strings" + + gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +// FindName returns the next filename available when the current +func FindName(ctx context.Context, client gatewayv1beta1.GatewayAPIClient, name string, parentid *provider.ResourceId) (string, *rpc.Status, error) { + lReq := &provider.ListContainerRequest{ + Ref: &provider.Reference{ + ResourceId: parentid, + }, + } + lRes, err := client.ListContainer(ctx, lReq) + if err != nil { + return "", nil, err + } + if lRes.Status.Code != rpc.Code_CODE_OK { + return "", lRes.Status, nil + } + // iterate over the listing to determine next suffix + var itemMap = make(map[string]struct{}) + for _, fi := range lRes.Infos { + itemMap[fi.GetName()] = struct{}{} + } + ext := filepath.Ext(name) + fileName := strings.TrimSuffix(name, ext) + if strings.HasSuffix(fileName, ".tar") { + fileName = strings.TrimSuffix(fileName, ".tar") + ext = filepath.Ext(fileName) + "." + ext + } + // starts with two because "normal" humans begin counting with 1 and we say the existing file is the first one + for i := 2; i < len(itemMap)+3; i++ { + if _, ok := itemMap[fileName+" ("+strconv.Itoa(i)+")"+ext]; !ok { + return fileName + " (" + strconv.Itoa(i) + ")" + ext, lRes.GetStatus(), nil + } + } + return "", nil, errors.New("could not determine new filename") +} diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/put.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/put.go index 485629a3f..5add5740d 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/put.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/put.go @@ -155,6 +155,47 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusInternalServerError) return } + + // Test if the target is a secret filedrop + tokenStatInfo, ok := TokenStatInfoFromContext(ctx) + // We assume that when the uploader can create containers, but is not allowed to list them, it is a secret file drop + if ok && tokenStatInfo.GetPermissionSet().CreateContainer && !tokenStatInfo.GetPermissionSet().ListContainer { + // TODO we can skip this stat if the tokenStatInfo is the direct parent + sReq := &provider.StatRequest{ + Ref: ref, + } + sRes, err := client.Stat(ctx, sReq) + if err != nil { + log.Error().Err(err).Msg("error sending grpc stat request") + w.WriteHeader(http.StatusInternalServerError) + return + } + + // We also need to continue if we are not allowed to stat a resource. We may not have stat permission. That still means it exists and we need to find a new filename. + switch sRes.Status.Code { + case rpc.Code_CODE_OK, rpc.Code_CODE_PERMISSION_DENIED: + // find next filename + newName, status, err := FindName(ctx, client, filepath.Base(ref.Path), sRes.GetInfo().GetParentId()) + if err != nil { + log.Error().Err(err).Msg("error sending grpc stat request") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + log.Error().Interface("status", status).Msg("error listing file") + errors.HandleErrorStatus(&log, w, status) + return + } + ref.Path = utils.MakeRelativePath(filepath.Join(filepath.Dir(ref.GetPath()), newName)) + case rpc.Code_CODE_NOT_FOUND: + // just continue with normal upload + default: + log.Error().Interface("status", sRes.Status).Msg("error stating file") + errors.HandleErrorStatus(&log, w, sRes.Status) + return + } + } + opaque := &typespb.Opaque{} if mtime := r.Header.Get(net.HeaderOCMtime); mtime != "" { utils.AppendPlainToOpaque(opaque, net.HeaderOCMtime, mtime) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go index 95ba36aa8..bd5831d4e 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/tus.go @@ -169,41 +169,20 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. } } if isSecretFileDrop { - lReq := &provider.ListContainerRequest{ - Ref: &provider.Reference{ - ResourceId: sRes.GetInfo().GetParentId(), - }, - } - lRes, err := client.ListContainer(ctx, lReq) + // find next filename + newName, status, err := FindName(ctx, client, filepath.Base(ref.Path), sRes.GetInfo().GetParentId()) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") w.WriteHeader(http.StatusInternalServerError) return } - if lRes.Status.Code != rpc.Code_CODE_OK { - log.Debug().Err(err).Msg("error listing container") - errors.HandleErrorStatus(&log, w, lRes.Status) + if status.GetCode() != rpc.Code_CODE_OK { + log.Error().Interface("status", status).Msg("error listing file") + errors.HandleErrorStatus(&log, w, status) return } - // iterate over the listing to determine next suffix - var itemMap = make(map[string]struct{}) - for _, fi := range lRes.Infos { - itemMap[fi.GetName()] = struct{}{} - } - ext := filepath.Ext(sRes.GetInfo().GetName()) - fileName := strings.TrimSuffix(sRes.GetInfo().GetName(), ext) - if strings.HasSuffix(fileName, ".tar") { - fileName = strings.TrimSuffix(fileName, ".tar") - ext = filepath.Ext(fileName) + "." + ext - } - // starts with two because "normal" humans begin counting with 1 and we say the existing file is the first one - for i := 2; i < len(itemMap)+3; i++ { - if _, ok := itemMap[fileName+" ("+strconv.Itoa(i)+")"+ext]; !ok { - sRes.GetInfo().Name = fileName + " (" + strconv.Itoa(i) + ")" + ext - ref.Path = filepath.Join(filepath.Dir(ref.GetPath()), sRes.GetInfo().GetName()) - break - } - } + ref.Path = filepath.Join(filepath.Dir(ref.GetPath()), newName) + sRes.GetInfo().Name = newName } } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/publicshare/manager/json/json.go b/vendor/github.com/cs3org/reva/v2/pkg/publicshare/manager/json/json.go index 7e809f552..1d2b44265 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/publicshare/manager/json/json.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/publicshare/manager/json/json.go @@ -72,10 +72,6 @@ func NewFile(c map[string]interface{}) (publicshare.Manager, error) { } p := file.New(conf.File) - if err := p.Init(context.Background()); err != nil { - return nil, err - } - return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p) } @@ -89,10 +85,6 @@ func NewMemory(c map[string]interface{}) (publicshare.Manager, error) { conf.init() p := memory.New() - if err := p.Init(context.Background()); err != nil { - return nil, err - } - return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p) } @@ -111,10 +103,6 @@ func NewCS3(c map[string]interface{}) (publicshare.Manager, error) { } p := cs3.New(s) - if err := p.Init(context.Background()); err != nil { - return nil, err - } - return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p) } @@ -174,6 +162,10 @@ type manager struct { enableExpiredSharesCleanup bool } +func (m *manager) init() error { + return m.persistence.Init(context.Background()) +} + func (m *manager) startJanitorRun() { if !m.enableExpiredSharesCleanup { return @@ -200,6 +192,10 @@ func (m *manager) Dump(ctx context.Context, shareChan chan<- *publicshare.WithPa m.mutex.Lock() defer m.mutex.Unlock() + if err := m.init(); err != nil { + return err + } + db, err := m.persistence.Read(ctx) if err != nil { return err @@ -222,6 +218,10 @@ func (m *manager) Load(ctx context.Context, shareChan <-chan *publicshare.WithPa m.mutex.Lock() defer m.mutex.Unlock() + if err := m.init(); err != nil { + return err + } + db, err := m.persistence.Read(ctx) if err != nil { return err @@ -296,6 +296,10 @@ func (m *manager) CreatePublicShare(ctx context.Context, u *user.User, rInfo *pr m.mutex.Lock() defer m.mutex.Unlock() + if err := m.init(); err != nil { + return nil, err + } + encShare, err := utils.MarshalProtoV1ToJSON(&ps.PublicShare) if err != nil { return nil, err @@ -385,6 +389,10 @@ func (m *manager) UpdatePublicShare(ctx context.Context, u *user.User, req *link m.mutex.Lock() defer m.mutex.Unlock() + if err := m.init(); err != nil { + return nil, err + } + db, err := m.persistence.Read(ctx) if err != nil { return nil, err @@ -420,6 +428,10 @@ func (m *manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.Pu m.mutex.Lock() defer m.mutex.Unlock() + if err := m.init(); err != nil { + return nil, err + } + if ref.GetToken() != "" { ps, pw, err := m.getByToken(ctx, ref.GetToken()) if err != nil { @@ -473,6 +485,10 @@ func (m *manager) ListPublicShares(ctx context.Context, u *user.User, filters [] m.mutex.Lock() defer m.mutex.Unlock() + if err := m.init(); err != nil { + return nil, err + } + log := appctx.GetLogger(ctx) db, err := m.persistence.Read(ctx) @@ -555,6 +571,10 @@ func (m *manager) cleanupExpiredShares() { m.mutex.Lock() defer m.mutex.Unlock() + if err := m.init(); err != nil { + return + } + db, _ := m.persistence.Read(context.Background()) for _, v := range db { @@ -594,6 +614,11 @@ func (m *manager) revokeExpiredPublicShare(ctx context.Context, s *link.PublicSh func (m *manager) RevokePublicShare(ctx context.Context, _ *user.User, ref *link.PublicShareReference) error { m.mutex.Lock() defer m.mutex.Unlock() + + if err := m.init(); err != nil { + return err + } + return m.revokePublicShare(ctx, ref) } @@ -651,6 +676,10 @@ func (m *manager) GetPublicShareByToken(ctx context.Context, token string, auth m.mutex.Lock() defer m.mutex.Unlock() + if err := m.init(); err != nil { + return nil, err + } + db, err := m.persistence.Read(ctx) if err != nil { return nil, err diff --git a/vendor/github.com/cs3org/reva/v2/pkg/publicshare/manager/json/persistence/file/file.go b/vendor/github.com/cs3org/reva/v2/pkg/publicshare/manager/json/persistence/file/file.go index d9a84d674..e813b95dc 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/publicshare/manager/json/persistence/file/file.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/publicshare/manager/json/persistence/file/file.go @@ -24,6 +24,7 @@ import ( "fmt" "os" "path/filepath" + "sync" "github.com/cs3org/reva/v2/pkg/publicshare/manager/json/persistence" ) @@ -31,16 +32,28 @@ import ( type file struct { path string initialized bool + lock *sync.RWMutex } // New returns a new Cache instance func New(path string) persistence.Persistence { return &file{ path: path, + lock: &sync.RWMutex{}, } } func (p *file) Init(_ context.Context) error { + if p.isInitialized() { + return nil + } + + p.lock.Lock() + defer p.lock.Unlock() + if p.initialized { + return nil + } + // attempt to create the db file var fi os.FileInfo var err error @@ -65,7 +78,7 @@ func (p *file) Init(_ context.Context) error { } func (p *file) Read(_ context.Context) (persistence.PublicShares, error) { - if !p.initialized { + if !p.isInitialized() { return nil, fmt.Errorf("not initialized") } db := map[string]interface{}{} @@ -80,7 +93,7 @@ func (p *file) Read(_ context.Context) (persistence.PublicShares, error) { } func (p *file) Write(_ context.Context, db persistence.PublicShares) error { - if !p.initialized { + if !p.isInitialized() { return fmt.Errorf("not initialized") } dbAsJSON, err := json.Marshal(db) @@ -90,3 +103,9 @@ func (p *file) Write(_ context.Context, db persistence.PublicShares) error { return os.WriteFile(p.path, dbAsJSON, 0644) } + +func (p *file) isInitialized() bool { + p.lock.RLock() + defer p.lock.RUnlock() + return p.initialized +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/loader/loader.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/loader/loader.go index 68575752c..71475e38a 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/loader/loader.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/loader/loader.go @@ -31,6 +31,7 @@ import ( _ "github.com/cs3org/reva/v2/pkg/storage/fs/nextcloud" _ "github.com/cs3org/reva/v2/pkg/storage/fs/ocis" _ "github.com/cs3org/reva/v2/pkg/storage/fs/owncloudsql" + _ "github.com/cs3org/reva/v2/pkg/storage/fs/posix" _ "github.com/cs3org/reva/v2/pkg/storage/fs/s3" _ "github.com/cs3org/reva/v2/pkg/storage/fs/s3ng" // Add your own here diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go new file mode 100644 index 000000000..7afd3eeb5 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go @@ -0,0 +1,122 @@ +// 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 blobstore + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" + "github.com/cs3org/reva/v2/pkg/utils" + "github.com/pkg/errors" +) + +// Blobstore provides an interface to an filesystem based blobstore +type Blobstore struct { + root string +} + +// New returns a new Blobstore +func New(root string) (*Blobstore, error) { + err := os.MkdirAll(root, 0700) + if err != nil { + return nil, err + } + + return &Blobstore{ + root: root, + }, nil +} + +// Upload stores some data in the blobstore under the given key +func (bs *Blobstore) Upload(node *node.Node, source string) error { + dest, err := bs.path(node) + if err != nil { + return err + } + // ensure parent path exists + if err := os.MkdirAll(filepath.Dir(dest), 0700); err != nil { + return errors.Wrap(err, "Decomposedfs: oCIS blobstore: error creating parent folders for blob") + } + + if err := os.Rename(source, dest); err == nil { + return nil + } + + // Rename failed, file needs to be copied. + file, err := os.Open(source) + if err != nil { + return errors.Wrap(err, "Decomposedfs: oCIS blobstore: Can not open source file to upload") + } + defer file.Close() + + f, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, 0700) + if err != nil { + return errors.Wrapf(err, "could not open blob '%s' for writing", dest) + } + + w := bufio.NewWriter(f) + _, err = w.ReadFrom(file) + if err != nil { + return errors.Wrapf(err, "could not write blob '%s'", dest) + } + + return w.Flush() +} + +// Download retrieves a blob from the blobstore for reading +func (bs *Blobstore) Download(node *node.Node) (io.ReadCloser, error) { + dest, err := bs.path(node) + if err != nil { + return nil, err + } + file, err := os.Open(dest) + if err != nil { + return nil, errors.Wrapf(err, "could not read blob '%s'", dest) + } + return file, nil +} + +// Delete deletes a blob from the blobstore +func (bs *Blobstore) Delete(node *node.Node) error { + dest, err := bs.path(node) + if err != nil { + return err + } + if err := utils.RemoveItem(dest); err != nil { + return errors.Wrapf(err, "could not delete blob '%s'", dest) + } + return nil +} + +func (bs *Blobstore) path(node *node.Node) (string, error) { + if node.BlobID == "" { + return "", fmt.Errorf("blobstore: BlobID is empty") + } + return filepath.Join( + bs.root, + filepath.Clean(filepath.Join( + "/", "spaces", lookup.Pathify(node.SpaceID, 1, 2), "blobs", lookup.Pathify(node.BlobID, 4, 2)), + ), + ), nil +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/lookup/lookup.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/lookup/lookup.go new file mode 100644 index 000000000..d64223f7d --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/lookup/lookup.go @@ -0,0 +1,375 @@ +// 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 lookup + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/appctx" + "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" + "github.com/cs3org/reva/v2/pkg/storage/utils/templates" + "github.com/pkg/errors" + "github.com/rogpeppe/go-internal/lockedfile" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" +) + +var tracer trace.Tracer + +var _spaceTypePersonal = "personal" + +func init() { + tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/lookup") +} + +// Lookup implements transformations from filepath to node and back +type Lookup struct { + Options *options.Options + + metadataBackend metadata.Backend +} + +// New returns a new Lookup instance +func New(b metadata.Backend, o *options.Options) *Lookup { + return &Lookup{ + Options: o, + metadataBackend: b, + } +} + +// MetadataBackend returns the metadata backend +func (lu *Lookup) MetadataBackend() metadata.Backend { + return lu.metadataBackend +} + +// ReadBlobSizeAttr reads the blobsize from the xattrs +func (lu *Lookup) ReadBlobSizeAttr(ctx context.Context, path string) (int64, error) { + blobSize, err := lu.metadataBackend.GetInt64(ctx, path, prefixes.BlobsizeAttr) + if err != nil { + return 0, errors.Wrapf(err, "error reading blobsize xattr") + } + return blobSize, nil +} + +// ReadBlobIDAttr reads the blobsize from the xattrs +func (lu *Lookup) ReadBlobIDAttr(ctx context.Context, path string) (string, error) { + attr, err := lu.metadataBackend.Get(ctx, path, prefixes.BlobIDAttr) + if err != nil { + return "", errors.Wrapf(err, "error reading blobid xattr") + } + return string(attr), nil +} + +// TypeFromPath returns the type of the node at the given path +func (lu *Lookup) TypeFromPath(ctx context.Context, path string) provider.ResourceType { + // Try to read from xattrs + typeAttr, err := lu.metadataBackend.GetInt64(ctx, path, prefixes.TypeAttr) + if err == nil { + return provider.ResourceType(int32(typeAttr)) + } + + t := provider.ResourceType_RESOURCE_TYPE_INVALID + // Fall back to checking on disk + fi, err := os.Lstat(path) + if err != nil { + return t + } + + switch { + case fi.IsDir(): + if _, err = lu.metadataBackend.Get(ctx, path, prefixes.ReferenceAttr); err == nil { + t = provider.ResourceType_RESOURCE_TYPE_REFERENCE + } else { + t = provider.ResourceType_RESOURCE_TYPE_CONTAINER + } + case fi.Mode().IsRegular(): + t = provider.ResourceType_RESOURCE_TYPE_FILE + case fi.Mode()&os.ModeSymlink != 0: + t = provider.ResourceType_RESOURCE_TYPE_SYMLINK + // TODO reference using ext attr on a symlink + // nodeType = provider.ResourceType_RESOURCE_TYPE_REFERENCE + } + return t +} + +// NodeFromResource takes in a request path or request id and converts it to a Node +func (lu *Lookup) NodeFromResource(ctx context.Context, ref *provider.Reference) (*node.Node, error) { + ctx, span := tracer.Start(ctx, "NodeFromResource") + defer span.End() + + if ref.ResourceId != nil { + // check if a storage space reference is used + // currently, the decomposed fs uses the root node id as the space id + n, err := lu.NodeFromID(ctx, ref.ResourceId) + if err != nil { + return nil, err + } + // is this a relative reference? + if ref.Path != "" { + p := filepath.Clean(ref.Path) + if p != "." && p != "/" { + // walk the relative path + n, err = lu.WalkPath(ctx, n, p, false, func(ctx context.Context, n *node.Node) error { return nil }) + if err != nil { + return nil, err + } + n.SpaceID = ref.ResourceId.SpaceId + } + } + return n, nil + } + + // reference is invalid + return nil, fmt.Errorf("invalid reference %+v. resource_id must be set", ref) +} + +// NodeFromID returns the internal path for the id +func (lu *Lookup) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error) { + ctx, span := tracer.Start(ctx, "NodeFromID") + defer span.End() + if id == nil { + return nil, fmt.Errorf("invalid resource id %+v", id) + } + if id.OpaqueId == "" { + // The Resource references the root of a space + return lu.NodeFromSpaceID(ctx, id.SpaceId) + } + return node.ReadNode(ctx, lu, id.SpaceId, id.OpaqueId, false, nil, false) +} + +// Pathify segments the beginning of a string into depth segments of width length +// Pathify("aabbccdd", 3, 1) will return "a/a/b/bccdd" +func Pathify(id string, depth, width int) string { + b := strings.Builder{} + i := 0 + for ; i < depth; i++ { + if len(id) <= i*width+width { + break + } + b.WriteString(id[i*width : i*width+width]) + b.WriteRune(filepath.Separator) + } + b.WriteString(id[i*width:]) + return b.String() +} + +// NodeFromSpaceID converts a resource id into a Node +func (lu *Lookup) NodeFromSpaceID(ctx context.Context, spaceID string) (n *node.Node, err error) { + node, err := node.ReadNode(ctx, lu, spaceID, spaceID, false, nil, false) + if err != nil { + return nil, err + } + + node.SpaceRoot = node + return node, nil +} + +// Path returns the path for node +func (lu *Lookup) Path(ctx context.Context, n *node.Node, hasPermission node.PermissionFunc) (p string, err error) { + root := n.SpaceRoot + for n.ID != root.ID { + p = filepath.Join(n.Name, p) + if n, err = n.Parent(ctx); err != nil { + appctx.GetLogger(ctx). + Error().Err(err). + Str("path", p). + Interface("node", n). + Msg("Path()") + return + } + + if !hasPermission(n) { + break + } + } + p = filepath.Join("/", p) + return +} + +// WalkPath calls n.Child(segment) on every path segment in p starting at the node r. +// If a function f is given it will be executed for every segment node, but not the root node r. +// If followReferences is given the current visited reference node is replaced by the referenced node. +func (lu *Lookup) WalkPath(ctx context.Context, r *node.Node, p string, followReferences bool, f func(ctx context.Context, n *node.Node) error) (*node.Node, error) { + segments := strings.Split(strings.Trim(p, "/"), "/") + var err error + for i := range segments { + if r, err = r.Child(ctx, segments[i]); err != nil { + return r, err + } + + if followReferences { + if attrBytes, err := r.Xattr(ctx, prefixes.ReferenceAttr); err == nil { + realNodeID := attrBytes + ref, err := refFromCS3(realNodeID) + if err != nil { + return nil, err + } + + r, err = lu.NodeFromID(ctx, ref.ResourceId) + if err != nil { + return nil, err + } + } + } + if r.IsSpaceRoot(ctx) { + r.SpaceRoot = r + } + + if !r.Exists && i < len(segments)-1 { + return r, errtypes.NotFound(segments[i]) + } + if f != nil { + if err = f(ctx, r); err != nil { + return r, err + } + } + } + return r, nil +} + +// InternalRoot returns the internal storage root directory +func (lu *Lookup) InternalRoot() string { + return lu.Options.Root +} + +// InternalPath returns the internal path for a given ID +func (lu *Lookup) InternalPath(spaceID, nodeID string) string { + return filepath.Join(lu.Options.Root, "spaces", Pathify(spaceID, 1, 2), "nodes", Pathify(nodeID, 4, 2)) +} + +func (lu *Lookup) SpacePath(spaceID string) string { + return filepath.Join(lu.Options.Root, spaceID) +} + +// // ReferenceFromAttr returns a CS3 reference from xattr of a node. +// // Supported formats are: "cs3:storageid/nodeid" +// func ReferenceFromAttr(b []byte) (*provider.Reference, error) { +// return refFromCS3(b) +// } + +// refFromCS3 creates a CS3 reference from a set of bytes. This method should remain private +// and only be called after validation because it can potentially panic. +func refFromCS3(b []byte) (*provider.Reference, error) { + parts := string(b[4:]) + return &provider.Reference{ + ResourceId: &provider.ResourceId{ + StorageId: strings.Split(parts, "/")[0], + OpaqueId: strings.Split(parts, "/")[1], + }, + }, nil +} + +// CopyMetadata copies all extended attributes from source to target. +// The optional filter function can be used to filter by attribute name, e.g. by checking a prefix +// For the source file, a shared lock is acquired. +// NOTE: target resource will be write locked! +func (lu *Lookup) CopyMetadata(ctx context.Context, src, target string, filter func(attributeName string, value []byte) (newValue []byte, copy bool), acquireTargetLock bool) (err error) { + // Acquire a read log on the source node + // write lock existing node before reading treesize or tree time + lock, err := lockedfile.OpenFile(lu.MetadataBackend().LockfilePath(src), os.O_RDONLY|os.O_CREATE, 0600) + if err != nil { + return err + } + + if err != nil { + return errors.Wrap(err, "xattrs: Unable to lock source to read") + } + defer func() { + rerr := lock.Close() + + // if err is non nil we do not overwrite that + if err == nil { + err = rerr + } + }() + + return lu.CopyMetadataWithSourceLock(ctx, src, target, filter, lock, acquireTargetLock) +} + +// CopyMetadataWithSourceLock copies all extended attributes from source to target. +// The optional filter function can be used to filter by attribute name, e.g. by checking a prefix +// For the source file, a matching lockedfile is required. +// NOTE: target resource will be write locked! +func (lu *Lookup) CopyMetadataWithSourceLock(ctx context.Context, sourcePath, targetPath string, filter func(attributeName string, value []byte) (newValue []byte, copy bool), lockedSource *lockedfile.File, acquireTargetLock bool) (err error) { + switch { + case lockedSource == nil: + return errors.New("no lock provided") + case lockedSource.File.Name() != lu.MetadataBackend().LockfilePath(sourcePath): + return errors.New("lockpath does not match filepath") + } + + attrs, err := lu.metadataBackend.All(ctx, sourcePath) + if err != nil { + return err + } + + newAttrs := make(map[string][]byte, 0) + for attrName, val := range attrs { + if filter != nil { + var ok bool + if val, ok = filter(attrName, val); !ok { + continue + } + } + newAttrs[attrName] = val + } + + return lu.MetadataBackend().SetMultiple(ctx, targetPath, newAttrs, acquireTargetLock) +} + +// GenerateSpaceID generates a space id for the given space type and owner +func (lu *Lookup) GenerateSpaceID(spaceType string, owner *user.User) (string, error) { + switch spaceType { + case _spaceTypePersonal: + return templates.WithUser(owner, lu.Options.UserLayout), nil + default: + return "", fmt.Errorf("unsupported space type: %s", spaceType) + } +} + +// DetectBackendOnDisk returns the name of the metadata backend being used on disk +func DetectBackendOnDisk(root string) string { + matches, _ := filepath.Glob(filepath.Join(root, "spaces", "*", "*")) + if len(matches) > 0 { + base := matches[len(matches)-1] + spaceid := strings.ReplaceAll( + strings.TrimPrefix(base, filepath.Join(root, "spaces")), + "/", "") + spaceRoot := Pathify(spaceid, 4, 2) + _, err := os.Stat(filepath.Join(base, "nodes", spaceRoot+".mpk")) + if err == nil { + return "mpk" + } + _, err = os.Stat(filepath.Join(base, "nodes", spaceRoot+".ini")) + if err == nil { + return "ini" + } + } + return "xattrs" +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/posix.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/posix.go new file mode 100644 index 000000000..f354f0a56 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/posix.go @@ -0,0 +1,100 @@ +// 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 posix + +import ( + "fmt" + "path" + + microstore "go-micro.dev/v4/store" + + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/v2/pkg/storage" + "github.com/cs3org/reva/v2/pkg/storage/fs/posix/blobstore" + "github.com/cs3org/reva/v2/pkg/storage/fs/posix/lookup" + "github.com/cs3org/reva/v2/pkg/storage/fs/posix/tree" + "github.com/cs3org/reva/v2/pkg/storage/fs/registry" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions" + "github.com/cs3org/reva/v2/pkg/store" +) + +func init() { + registry.Register("posix", New) +} + +// New returns an implementation to of the storage.FS interface that talk to +// a local filesystem. +func New(m map[string]interface{}, stream events.Stream) (storage.FS, error) { + o, err := options.New(m) + if err != nil { + return nil, err + } + + bs, err := blobstore.New(path.Join(o.Root)) + if err != nil { + return nil, err + } + + var lu *lookup.Lookup + switch o.MetadataBackend { + case "xattrs": + lu = lookup.New(metadata.XattrsBackend{}, o) + case "messagepack": + lu = lookup.New(metadata.NewMessagePackBackend(o.Root, o.FileMetadataCache), o) + default: + return nil, fmt.Errorf("unknown metadata backend %s, only 'messagepack' or 'xattrs' (default) supported", o.MetadataBackend) + } + + tp := tree.New(lu, bs, o, store.Create( + store.Store(o.IDCache.Store), + store.TTL(o.IDCache.TTL), + store.Size(o.IDCache.Size), + microstore.Nodes(o.IDCache.Nodes...), + microstore.Database(o.IDCache.Database), + microstore.Table(o.IDCache.Table), + store.DisablePersistence(o.IDCache.DisablePersistence), + store.Authentication(o.IDCache.AuthUsername, o.IDCache.AuthPassword), + )) + + permissionsSelector, err := pool.PermissionsSelector(o.PermissionsSVC, pool.WithTLSMode(o.PermTLSMode)) + if err != nil { + return nil, err + } + + p := permissions.NewPermissions(node.NewPermissions(lu), permissionsSelector) + + aspects := aspects.Aspects{ + Lookup: lu, + Tree: tp, + Permissions: p, + EventStream: stream, + } + fs, err := decomposedfs.New(o, aspects) + if err != nil { + return nil, err + } + + return fs, nil +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/tree/tree.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/tree/tree.go new file mode 100644 index 000000000..bf6eeb70c --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/fs/posix/tree/tree.go @@ -0,0 +1,799 @@ +// 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 tree + +import ( + "bytes" + "context" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/appctx" + "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator" + "github.com/cs3org/reva/v2/pkg/utils" + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "go-micro.dev/v4/store" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" + "golang.org/x/sync/errgroup" +) + +var tracer trace.Tracer + +func init() { + tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/tree") +} + +//go:generate make --no-print-directory -C ../../../../.. mockery NAME=Blobstore + +// Blobstore defines an interface for storing blobs in a blobstore +type Blobstore interface { + Upload(node *node.Node, source string) error + Download(node *node.Node) (io.ReadCloser, error) + Delete(node *node.Node) error +} + +// Tree manages a hierarchical tree +type Tree struct { + lookup node.PathLookup + blobstore Blobstore + propagator propagator.Propagator + + options *options.Options + + idCache store.Store +} + +// PermissionCheckFunc defined a function used to check resource permissions +type PermissionCheckFunc func(rp *provider.ResourcePermissions) bool + +// New returns a new instance of Tree +func New(lu node.PathLookup, bs Blobstore, o *options.Options, cache store.Store) *Tree { + return &Tree{ + lookup: lu, + blobstore: bs, + options: o, + idCache: cache, + propagator: propagator.New(lu, o), + } +} + +// Setup prepares the tree structure +func (t *Tree) Setup() error { + return os.MkdirAll(t.options.Root, 0700) +} + +// GetMD returns the metadata of a node in the tree +func (t *Tree) GetMD(ctx context.Context, n *node.Node) (os.FileInfo, error) { + md, err := os.Stat(n.InternalPath()) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return nil, errtypes.NotFound(n.ID) + } + return nil, errors.Wrap(err, "tree: error stating "+n.ID) + } + + return md, nil +} + +// TouchFile creates a new empty file +func (t *Tree) TouchFile(ctx context.Context, n *node.Node, markprocessing bool, mtime string) error { + if n.Exists { + if markprocessing { + return n.SetXattr(ctx, prefixes.StatusPrefix, []byte(node.ProcessingStatus)) + } + + return errtypes.AlreadyExists(n.ID) + } + + if n.ID == "" { + n.ID = uuid.New().String() + } + n.SetType(provider.ResourceType_RESOURCE_TYPE_FILE) + + nodePath := n.InternalPath() + if err := os.MkdirAll(filepath.Dir(nodePath), 0700); err != nil { + return errors.Wrap(err, "Decomposedfs: error creating node") + } + _, err := os.Create(nodePath) + if err != nil { + return errors.Wrap(err, "Decomposedfs: error creating node") + } + + attributes := n.NodeMetadata(ctx) + if markprocessing { + attributes[prefixes.StatusPrefix] = []byte(node.ProcessingStatus) + } + if mtime != "" { + if err := n.SetMtimeString(ctx, mtime); err != nil { + return errors.Wrap(err, "Decomposedfs: could not set mtime") + } + } else { + now := time.Now() + if err := n.SetMtime(ctx, &now); err != nil { + return errors.Wrap(err, "Decomposedfs: could not set mtime") + } + } + err = n.SetXattrsWithContext(ctx, attributes, true) + if err != nil { + return err + } + + return t.Propagate(ctx, n, 0) +} + +// CreateDir creates a new directory entry in the tree +func (t *Tree) CreateDir(ctx context.Context, n *node.Node) (err error) { + ctx, span := tracer.Start(ctx, "CreateDir") + defer span.End() + if n.Exists { + return errtypes.AlreadyExists(n.ID) // path? + } + + // create a directory node + n.SetType(provider.ResourceType_RESOURCE_TYPE_CONTAINER) + if n.ID == "" { + n.ID = uuid.New().String() + } + + err = t.createDirNode(ctx, n) + if err != nil { + return + } + + return t.Propagate(ctx, n, 0) +} + +// Move replaces the target with the source +func (t *Tree) Move(ctx context.Context, oldNode *node.Node, newNode *node.Node) (err error) { + if oldNode.SpaceID != newNode.SpaceID { + // WebDAV RFC https://www.rfc-editor.org/rfc/rfc4918#section-9.9.4 says to use + // > 502 (Bad Gateway) - This may occur when the destination is on another + // > server and the destination server refuses to accept the resource. + // > This could also occur when the destination is on another sub-section + // > of the same server namespace. + // but we only have a not supported error + return errtypes.NotSupported("cannot move across spaces") + } + // if target exists delete it without trashing it + if newNode.Exists { + // TODO make sure all children are deleted + if err := os.RemoveAll(newNode.InternalPath()); err != nil { + return errors.Wrap(err, "Decomposedfs: Move: error deleting target node "+newNode.ID) + } + } + + // remove cache entry in any case to avoid inconsistencies + defer func() { _ = t.idCache.Delete(filepath.Join(oldNode.ParentPath(), oldNode.Name)) }() + + // Always target the old node ID for xattr updates. + // The new node id is empty if the target does not exist + // and we need to overwrite the new one when overwriting an existing path. + // are we just renaming (parent stays the same)? + if oldNode.ParentID == newNode.ParentID { + + // parentPath := t.lookup.InternalPath(oldNode.SpaceID, oldNode.ParentID) + parentPath := oldNode.ParentPath() + + // rename child + err = os.Rename( + filepath.Join(parentPath, oldNode.Name), + filepath.Join(parentPath, newNode.Name), + ) + if err != nil { + return errors.Wrap(err, "Decomposedfs: could not rename child") + } + + // update name attribute + if err := oldNode.SetXattrString(ctx, prefixes.NameAttr, newNode.Name); err != nil { + return errors.Wrap(err, "Decomposedfs: could not set name attribute") + } + + return t.Propagate(ctx, newNode, 0) + } + + // we are moving the node to a new parent, any target has been removed + // bring old node to the new parent + + // rename child + err = os.Rename( + filepath.Join(oldNode.ParentPath(), oldNode.Name), + filepath.Join(newNode.ParentPath(), newNode.Name), + ) + if err != nil { + return errors.Wrap(err, "Decomposedfs: could not move child") + } + + // update target parentid and name + attribs := node.Attributes{} + attribs.SetString(prefixes.ParentidAttr, newNode.ParentID) + attribs.SetString(prefixes.NameAttr, newNode.Name) + if err := oldNode.SetXattrsWithContext(ctx, attribs, true); err != nil { + return errors.Wrap(err, "Decomposedfs: could not update old node attributes") + } + + // the size diff is the current treesize or blobsize of the old/source node + var sizeDiff int64 + if oldNode.IsDir(ctx) { + treeSize, err := oldNode.GetTreeSize(ctx) + if err != nil { + return err + } + sizeDiff = int64(treeSize) + } else { + sizeDiff = oldNode.Blobsize + } + + // TODO inefficient because we might update several nodes twice, only propagate unchanged nodes? + // collect in a list, then only stat each node once + // also do this in a go routine ... webdav should check the etag async + + err = t.Propagate(ctx, oldNode, -sizeDiff) + if err != nil { + return errors.Wrap(err, "Decomposedfs: Move: could not propagate old node") + } + err = t.Propagate(ctx, newNode, sizeDiff) + if err != nil { + return errors.Wrap(err, "Decomposedfs: Move: could not propagate new node") + } + return nil +} + +func readChildNodeFromLink(ctx context.Context, path string) (string, error) { + _, span := tracer.Start(ctx, "readChildNodeFromLink") + defer span.End() + link, err := os.Readlink(path) + if err != nil { + return "", err + } + nodeID := strings.TrimLeft(link, "/.") + nodeID = strings.ReplaceAll(nodeID, "/", "") + return nodeID, nil +} + +// ListFolder lists the content of a folder node +func (t *Tree) ListFolder(ctx context.Context, n *node.Node) ([]*node.Node, error) { + ctx, span := tracer.Start(ctx, "ListFolder") + defer span.End() + dir := n.InternalPath() + + _, subspan := tracer.Start(ctx, "os.Open") + f, err := os.Open(dir) + subspan.End() + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return nil, errtypes.NotFound(dir) + } + return nil, errors.Wrap(err, "tree: error listing "+dir) + } + defer f.Close() + + _, subspan = tracer.Start(ctx, "f.Readdirnames") + names, err := f.Readdirnames(0) + subspan.End() + if err != nil { + return nil, err + } + + numWorkers := t.options.MaxConcurrency + if len(names) < numWorkers { + numWorkers = len(names) + } + work := make(chan string) + results := make(chan *node.Node) + + g, ctx := errgroup.WithContext(ctx) + + // Distribute work + g.Go(func() error { + defer close(work) + for _, name := range names { + select { + case work <- name: + case <-ctx.Done(): + return ctx.Err() + } + } + return nil + }) + + // Spawn workers that'll concurrently work the queue + for i := 0; i < numWorkers; i++ { + g.Go(func() error { + var err error + for name := range work { + path := filepath.Join(dir, name) + nodeID := getNodeIDFromCache(ctx, path, t.idCache) + if nodeID == "" { + nodeID, err = readChildNodeFromLink(ctx, path) + if err != nil { + return err + } + err = storeNodeIDInCache(ctx, path, nodeID, t.idCache) + if err != nil { + return err + } + } + + child, err := node.ReadNode(ctx, t.lookup, n.SpaceID, nodeID, false, n.SpaceRoot, true) + if err != nil { + return err + } + + // prevent listing denied resources + if !child.IsDenied(ctx) { + if child.SpaceRoot == nil { + child.SpaceRoot = n.SpaceRoot + } + select { + case results <- child: + case <-ctx.Done(): + return ctx.Err() + } + } + } + return nil + }) + } + // Wait for things to settle down, then close results chan + go func() { + _ = g.Wait() // error is checked later + close(results) + }() + + retNodes := []*node.Node{} + for n := range results { + retNodes = append(retNodes, n) + } + + if err := g.Wait(); err != nil { + return nil, err + } + + return retNodes, nil +} + +// Delete deletes a node in the tree by moving it to the trash +func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) { + path := filepath.Join(n.ParentPath(), n.Name) + // remove entry from cache immediately to avoid inconsistencies + defer func() { _ = t.idCache.Delete(path) }() + + deletingSharedResource := ctx.Value(appctx.DeletingSharedResource) + + if deletingSharedResource != nil && deletingSharedResource.(bool) { + src := filepath.Join(n.ParentPath(), n.Name) + return os.Remove(src) + } + + // get the original path + origin, err := t.lookup.Path(ctx, n, node.NoCheck) + if err != nil { + return + } + + // set origin location in metadata + nodePath := n.InternalPath() + if err := n.SetXattrString(ctx, prefixes.TrashOriginAttr, origin); err != nil { + return err + } + + var sizeDiff int64 + if n.IsDir(ctx) { + treesize, err := n.GetTreeSize(ctx) + if err != nil { + return err // TODO calculate treesize if it is not set + } + sizeDiff = -int64(treesize) + } else { + sizeDiff = -n.Blobsize + } + + deletionTime := time.Now().UTC().Format(time.RFC3339Nano) + + // Prepare the trash + trashLink := filepath.Join(t.options.Root, "spaces", lookup.Pathify(n.SpaceRoot.ID, 1, 2), "trash", lookup.Pathify(n.ID, 4, 2)) + if err := os.MkdirAll(filepath.Dir(trashLink), 0700); err != nil { + // Roll back changes + _ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true) + return err + } + + // FIXME can we just move the node into the trash dir? instead of adding another symlink and appending a trash timestamp? + // can we just use the mtime as the trash time? + // TODO store a trashed by userid + + // first make node appear in the space trash + // parent id and name are stored as extended attributes in the node itself + err = os.Symlink("../../../../../nodes/"+lookup.Pathify(n.ID, 4, 2)+node.TrashIDDelimiter+deletionTime, trashLink) + if err != nil { + // Roll back changes + _ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true) + return + } + + // at this point we have a symlink pointing to a non existing destination, which is fine + + // rename the trashed node so it is not picked up when traversing up the tree and matches the symlink + trashPath := nodePath + node.TrashIDDelimiter + deletionTime + err = os.Rename(nodePath, trashPath) + if err != nil { + // To roll back changes + // TODO remove symlink + // Roll back changes + _ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true) + return + } + err = t.lookup.MetadataBackend().Rename(nodePath, trashPath) + if err != nil { + _ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true) + _ = os.Rename(trashPath, nodePath) + return + } + + // Remove lock file if it exists + _ = os.Remove(n.LockFilePath()) + + // finally remove the entry from the parent dir + if err = os.Remove(path); err != nil { + // To roll back changes + // TODO revert the rename + // TODO remove symlink + // Roll back changes + _ = n.RemoveXattr(ctx, prefixes.TrashOriginAttr, true) + return + } + + return t.Propagate(ctx, n, sizeDiff) +} + +// RestoreRecycleItemFunc returns a node and a function to restore it from the trash. +func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPath string, targetNode *node.Node) (*node.Node, *node.Node, func() error, error) { + recycleNode, trashItem, deletedNodePath, origin, err := t.readRecycleItem(ctx, spaceid, key, trashPath) + if err != nil { + return nil, nil, nil, err + } + + targetRef := &provider.Reference{ + ResourceId: &provider.ResourceId{SpaceId: spaceid, OpaqueId: spaceid}, + Path: utils.MakeRelativePath(origin), + } + + if targetNode == nil { + targetNode, err = t.lookup.NodeFromResource(ctx, targetRef) + if err != nil { + return nil, nil, nil, err + } + } + + if err := targetNode.CheckLock(ctx); err != nil { + return nil, nil, nil, err + } + + parent, err := targetNode.Parent(ctx) + if err != nil { + return nil, nil, nil, err + } + + fn := func() error { + if targetNode.Exists { + return errtypes.AlreadyExists("origin already exists") + } + + // add the entry for the parent dir + err = os.Symlink("../../../../../"+lookup.Pathify(recycleNode.ID, 4, 2), filepath.Join(targetNode.ParentPath(), targetNode.Name)) + if err != nil { + return err + } + + // rename to node only name, so it is picked up by id + nodePath := recycleNode.InternalPath() + + // attempt to rename only if we're not in a subfolder + if deletedNodePath != nodePath { + err = os.Rename(deletedNodePath, nodePath) + if err != nil { + return err + } + err = t.lookup.MetadataBackend().Rename(deletedNodePath, nodePath) + if err != nil { + return err + } + } + + targetNode.Exists = true + + attrs := node.Attributes{} + attrs.SetString(prefixes.NameAttr, targetNode.Name) + if trashPath != "" { + // set ParentidAttr to restorePath's node parent id + attrs.SetString(prefixes.ParentidAttr, targetNode.ParentID) + } + + if err = recycleNode.SetXattrsWithContext(ctx, attrs, true); err != nil { + return errors.Wrap(err, "Decomposedfs: could not update recycle node") + } + + // delete item link in trash + deletePath := trashItem + if trashPath != "" && trashPath != "/" { + resolvedTrashRoot, err := filepath.EvalSymlinks(trashItem) + if err != nil { + return errors.Wrap(err, "Decomposedfs: could not resolve trash root") + } + deletePath = filepath.Join(resolvedTrashRoot, trashPath) + } + if err = os.Remove(deletePath); err != nil { + log.Error().Err(err).Str("trashItem", trashItem).Msg("error deleting trash item") + } + + var sizeDiff int64 + if recycleNode.IsDir(ctx) { + treeSize, err := recycleNode.GetTreeSize(ctx) + if err != nil { + return err + } + sizeDiff = int64(treeSize) + } else { + sizeDiff = recycleNode.Blobsize + } + return t.Propagate(ctx, targetNode, sizeDiff) + } + return recycleNode, parent, fn, nil +} + +// PurgeRecycleItemFunc returns a node and a function to purge it from the trash +func (t *Tree) PurgeRecycleItemFunc(ctx context.Context, spaceid, key string, path string) (*node.Node, func() error, error) { + rn, trashItem, deletedNodePath, _, err := t.readRecycleItem(ctx, spaceid, key, path) + if err != nil { + return nil, nil, err + } + + fn := func() error { + if err := t.removeNode(ctx, deletedNodePath, rn); err != nil { + return err + } + + // delete item link in trash + deletePath := trashItem + if path != "" && path != "/" { + resolvedTrashRoot, err := filepath.EvalSymlinks(trashItem) + if err != nil { + return errors.Wrap(err, "Decomposedfs: could not resolve trash root") + } + deletePath = filepath.Join(resolvedTrashRoot, path) + } + if err = os.Remove(deletePath); err != nil { + log.Error().Err(err).Str("deletePath", deletePath).Msg("error deleting trash item") + return err + } + + return nil + } + + return rn, fn, nil +} + +func (t *Tree) removeNode(ctx context.Context, path string, n *node.Node) error { + // delete the actual node + if err := utils.RemoveItem(path); err != nil { + log.Error().Err(err).Str("path", path).Msg("error purging node") + return err + } + + if err := t.lookup.MetadataBackend().Purge(path); err != nil { + log.Error().Err(err).Str("path", t.lookup.MetadataBackend().MetadataPath(path)).Msg("error purging node metadata") + return err + } + + // delete blob from blobstore + if n.BlobID != "" { + if err := t.DeleteBlob(n); err != nil { + log.Error().Err(err).Str("blobID", n.BlobID).Msg("error purging nodes blob") + return err + } + } + + // delete revisions + revs, err := filepath.Glob(n.InternalPath() + node.RevisionIDDelimiter + "*") + if err != nil { + log.Error().Err(err).Str("path", n.InternalPath()+node.RevisionIDDelimiter+"*").Msg("glob failed badly") + return err + } + for _, rev := range revs { + if t.lookup.MetadataBackend().IsMetaFile(rev) { + continue + } + + bID, err := t.lookup.ReadBlobIDAttr(ctx, rev) + if err != nil { + log.Error().Err(err).Str("revision", rev).Msg("error reading blobid attribute") + return err + } + + if err := utils.RemoveItem(rev); err != nil { + log.Error().Err(err).Str("revision", rev).Msg("error removing revision node") + return err + } + + if bID != "" { + if err := t.DeleteBlob(&node.Node{SpaceID: n.SpaceID, BlobID: bID}); err != nil { + log.Error().Err(err).Str("revision", rev).Str("blobID", bID).Msg("error removing revision node blob") + return err + } + } + + } + + return nil +} + +// Propagate propagates changes to the root of the tree +func (t *Tree) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) (err error) { + return t.propagator.Propagate(ctx, n, sizeDiff) +} + +// WriteBlob writes a blob to the blobstore +func (t *Tree) WriteBlob(node *node.Node, source string) error { + return t.blobstore.Upload(node, source) +} + +// ReadBlob reads a blob from the blobstore +func (t *Tree) ReadBlob(node *node.Node) (io.ReadCloser, error) { + if node.BlobID == "" { + // there is no blob yet - we are dealing with a 0 byte file + return io.NopCloser(bytes.NewReader([]byte{})), nil + } + return t.blobstore.Download(node) +} + +// DeleteBlob deletes a blob from the blobstore +func (t *Tree) DeleteBlob(node *node.Node) error { + if node == nil { + return fmt.Errorf("could not delete blob, nil node was given") + } + if node.BlobID == "" { + return fmt.Errorf("could not delete blob, node with empty blob id was given") + } + + return t.blobstore.Delete(node) +} + +// TODO check if node exists? +func (t *Tree) createDirNode(ctx context.Context, n *node.Node) (err error) { + ctx, span := tracer.Start(ctx, "createDirNode") + defer span.End() + // create a directory node + nodePath := n.InternalPath() + if err := os.MkdirAll(nodePath, 0700); err != nil { + return errors.Wrap(err, "Decomposedfs: error creating node") + } + + attributes := n.NodeMetadata(ctx) + attributes[prefixes.TreesizeAttr] = []byte("0") // initialize as empty, TODO why bother? if it is not set we could treat it as 0? + if t.options.TreeTimeAccounting || t.options.TreeSizeAccounting { + attributes[prefixes.PropagationAttr] = []byte("1") // mark the node for propagation + } + return n.SetXattrsWithContext(ctx, attributes, true) +} + +var nodeIDRegep = regexp.MustCompile(`.*/nodes/([^.]*).*`) + +// TODO refactor the returned params into Node properties? would make all the path transformations go away... +func (t *Tree) readRecycleItem(ctx context.Context, spaceID, key, path string) (recycleNode *node.Node, trashItem string, deletedNodePath string, origin string, err error) { + if key == "" { + return nil, "", "", "", errtypes.InternalError("key is empty") + } + + backend := t.lookup.MetadataBackend() + var nodeID string + + trashItem = filepath.Join(t.lookup.InternalRoot(), "spaces", lookup.Pathify(spaceID, 1, 2), "trash", lookup.Pathify(key, 4, 2)) + resolvedTrashItem, err := filepath.EvalSymlinks(trashItem) + if err != nil { + return + } + deletedNodePath, err = filepath.EvalSymlinks(filepath.Join(resolvedTrashItem, path)) + if err != nil { + return + } + nodeID = nodeIDRegep.ReplaceAllString(deletedNodePath, "$1") + nodeID = strings.ReplaceAll(nodeID, "/", "") + + recycleNode = node.New(spaceID, nodeID, "", "", 0, "", provider.ResourceType_RESOURCE_TYPE_INVALID, nil, t.lookup) + recycleNode.SpaceRoot, err = node.ReadNode(ctx, t.lookup, spaceID, spaceID, false, nil, false) + if err != nil { + return + } + recycleNode.SetType(t.lookup.TypeFromPath(ctx, deletedNodePath)) + + var attrBytes []byte + if recycleNode.Type(ctx) == provider.ResourceType_RESOURCE_TYPE_FILE { + // lookup blobID in extended attributes + if attrBytes, err = backend.Get(ctx, deletedNodePath, prefixes.BlobIDAttr); err == nil { + recycleNode.BlobID = string(attrBytes) + } else { + return + } + + // lookup blobSize in extended attributes + if recycleNode.Blobsize, err = backend.GetInt64(ctx, deletedNodePath, prefixes.BlobsizeAttr); err != nil { + return + } + } + + // lookup parent id in extended attributes + if attrBytes, err = backend.Get(ctx, deletedNodePath, prefixes.ParentidAttr); err == nil { + recycleNode.ParentID = string(attrBytes) + } else { + return + } + + // lookup name in extended attributes + if attrBytes, err = backend.Get(ctx, deletedNodePath, prefixes.NameAttr); err == nil { + recycleNode.Name = string(attrBytes) + } else { + return + } + + // get origin node, is relative to space root + origin = "/" + + // lookup origin path in extended attributes + if attrBytes, err = backend.Get(ctx, resolvedTrashItem, prefixes.TrashOriginAttr); err == nil { + origin = filepath.Join(string(attrBytes), path) + } else { + log.Error().Err(err).Str("trashItem", trashItem).Str("deletedNodePath", deletedNodePath).Msg("could not read origin path, restoring to /") + } + + return +} + +func getNodeIDFromCache(ctx context.Context, path string, cache store.Store) string { + _, span := tracer.Start(ctx, "getNodeIDFromCache") + defer span.End() + recs, err := cache.Read(path) + if err == nil && len(recs) > 0 { + return string(recs[0].Value) + } + return "" +} + +func storeNodeIDInCache(ctx context.Context, path string, nodeID string, cache store.Store) error { + _, span := tracer.Start(ctx, "storeNodeIDInCache") + defer span.End() + return cache.Write(&store.Record{ + Key: path, + Value: []byte(nodeID), + }) +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects/aspects.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects/aspects.go new file mode 100644 index 000000000..28ee4495a --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects/aspects.go @@ -0,0 +1,33 @@ +// Copyright 2018-2024 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 aspects + +import ( + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions" +) + +// Aspects holds dependencies for handling aspects of the decomposedfs +type Aspects struct { + Lookup node.PathLookup + Tree node.Tree + Permissions permissions.Permissions + EventStream events.Stream +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go index bedb629ee..35cc29663 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -18,16 +18,11 @@ package decomposedfs -//go:generate make --no-print-directory -C ../../../.. mockery NAME=PermissionsChecker -//go:generate make --no-print-directory -C ../../../.. mockery NAME=CS3PermissionsClient -//go:generate make --no-print-directory -C ../../../.. mockery NAME=Tree - import ( "context" "fmt" "io" "net/url" - "os" "path" "path/filepath" "strconv" @@ -47,11 +42,13 @@ import ( "github.com/cs3org/reva/v2/pkg/storage" "github.com/cs3org/reva/v2/pkg/storage/cache" "github.com/cs3org/reva/v2/pkg/storage/utils/chunking" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/migrator" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaceidindex" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload" @@ -83,28 +80,6 @@ func init() { tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs") } -// Tree is used to manage a tree hierarchy -type Tree interface { - Setup() error - - GetMD(ctx context.Context, node *node.Node) (os.FileInfo, error) - ListFolder(ctx context.Context, node *node.Node) ([]*node.Node, error) - // CreateHome(owner *userpb.UserId) (n *node.Node, err error) - CreateDir(ctx context.Context, node *node.Node) (err error) - TouchFile(ctx context.Context, node *node.Node, markprocessing bool, mtime string) error - // CreateReference(ctx context.Context, node *node.Node, targetURI *url.URL) error - Move(ctx context.Context, oldNode *node.Node, newNode *node.Node) (err error) - Delete(ctx context.Context, node *node.Node) (err error) - RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPath string, target *node.Node) (*node.Node, *node.Node, func() error, error) - PurgeRecycleItemFunc(ctx context.Context, spaceid, key, purgePath string) (*node.Node, func() error, error) - - WriteBlob(node *node.Node, source string) error - ReadBlob(node *node.Node) (io.ReadCloser, error) - DeleteBlob(node *node.Node) error - - Propagate(ctx context.Context, node *node.Node, sizeDiff int64) (err error) -} - // Session is the interface that OcisSession implements. By combining tus.Upload, // storage.UploadSession and custom functions we can reuse the same struct throughout // the whole upload lifecycle. @@ -127,10 +102,10 @@ type SessionStore interface { // Decomposedfs provides the base for decomposed filesystem implementations type Decomposedfs struct { - lu *lookup.Lookup - tp Tree + lu node.PathLookup + tp node.Tree o *options.Options - p Permissions + p permissions.Permissions chunkHandler *chunking.ChunkHandler stream events.Stream cache cache.StatCache @@ -175,23 +150,29 @@ func NewDefault(m map[string]interface{}, bs tree.Blobstore, es events.Stream) ( return nil, err } - permissions := NewPermissions(node.NewPermissions(lu), permissionsSelector) + aspects := aspects.Aspects{ + Lookup: lu, + Tree: tp, + Permissions: permissions.NewPermissions(node.NewPermissions(lu), permissionsSelector), + EventStream: es, + } - return New(o, lu, permissions, tp, es) + return New(o, aspects) } // New returns an implementation of the storage.FS interface that talks to // a local filesystem. -func New(o *options.Options, lu *lookup.Lookup, p Permissions, tp Tree, es events.Stream) (storage.FS, error) { +func New(o *options.Options, aspects aspects.Aspects) (storage.FS, error) { log := logger.New() - err := tp.Setup() + + err := aspects.Tree.Setup() if err != nil { log.Error().Err(err).Msg("could not setup tree") return nil, errors.Wrap(err, "could not setup tree") } // Run migrations & return - m := migrator.New(lu, log) + m := migrator.New(aspects.Lookup, log) err = m.RunMigrations() if err != nil { log.Error().Err(err).Msg("could not migrate tree") @@ -222,18 +203,18 @@ func New(o *options.Options, lu *lookup.Lookup, p Permissions, tp Tree, es event } fs := &Decomposedfs{ - tp: tp, - lu: lu, + tp: aspects.Tree, + lu: aspects.Lookup, o: o, - p: p, + p: aspects.Permissions, chunkHandler: chunking.NewChunkHandler(filepath.Join(o.Root, "uploads")), - stream: es, + stream: aspects.EventStream, cache: cache.GetStatCache(o.StatCache), UserCache: ttlcache.NewCache(), userSpaceIndex: userSpaceIndex, groupSpaceIndex: groupSpaceIndex, spaceTypeIndex: spaceTypeIndex, - sessionStore: upload.NewSessionStore(lu, tp, o.Root, es, o.AsyncFileUploads, o.Tokens), + sessionStore: upload.NewSessionStore(aspects.Lookup, aspects.Tree, o.Root, aspects.EventStream, o.AsyncFileUploads, o.Tokens), } if o.AsyncFileUploads { diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go index 9e0ffd55b..bd432c5fd 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go @@ -25,6 +25,7 @@ import ( "path/filepath" "strings" + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/appctx" "github.com/cs3org/reva/v2/pkg/errtypes" @@ -32,6 +33,7 @@ import ( "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" + "github.com/google/uuid" "github.com/pkg/errors" "github.com/rogpeppe/go-internal/lockedfile" "go.opentelemetry.io/otel" @@ -40,24 +42,14 @@ import ( var tracer trace.Tracer +const ( + _spaceTypePersonal = "personal" +) + func init() { tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/lookup") } -// PathLookup defines the interface for the lookup component -type PathLookup interface { - NodeFromResource(ctx context.Context, ref *provider.Reference) (*node.Node, error) - NodeFromID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error) - - InternalRoot() string - InternalPath(spaceID, nodeID string) string - Path(ctx context.Context, n *node.Node, hasPermission node.PermissionFunc) (path string, err error) - MetadataBackend() metadata.Backend - ReadBlobSizeAttr(ctx context.Context, path string) (int64, error) - ReadBlobIDAttr(ctx context.Context, path string) (string, error) - TypeFromPath(ctx context.Context, path string) provider.ResourceType -} - // Lookup implements transformations from filepath to node and back type Lookup struct { Options *options.Options @@ -200,6 +192,16 @@ func (lu *Lookup) NodeFromSpaceID(ctx context.Context, spaceID string) (n *node. return node, nil } +// GenerateSpaceID generates a new space id and alias +func (lu *Lookup) GenerateSpaceID(spaceType string, owner *user.User) (string, error) { + switch spaceType { + case _spaceTypePersonal: + return owner.Id.OpaqueId, nil + default: + return uuid.New().String(), nil + } +} + // Path returns the path for node func (lu *Lookup) Path(ctx context.Context, n *node.Node, hasPermission node.PermissionFunc) (p string, err error) { root := n.SpaceRoot diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/migrator/migrator.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/migrator/migrator.go index c164d6fa5..50c40b3b3 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/migrator/migrator.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/migrator/migrator.go @@ -25,7 +25,7 @@ import ( "path/filepath" "sort" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" "github.com/rogpeppe/go-internal/lockedfile" "github.com/rs/zerolog" ) @@ -73,13 +73,13 @@ type Result string // Migrator runs migrations on an existing decomposedfs type Migrator struct { - lu *lookup.Lookup + lu node.PathLookup states migrationStates log *zerolog.Logger } // New returns a new Migrator instance -func New(lu *lookup.Lookup, log *zerolog.Logger) Migrator { +func New(lu node.PathLookup, log *zerolog.Logger) Migrator { return Migrator{ lu: lu, log: log, diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/node.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/node.go index 3a9f259d9..f201722da 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/node.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/node.go @@ -49,10 +49,13 @@ import ( "github.com/cs3org/reva/v2/pkg/utils" "github.com/google/uuid" "github.com/pkg/errors" + "github.com/rogpeppe/go-internal/lockedfile" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" ) +//go:generate make --no-print-directory -C ../../../../.. mockery NAME=Tree + var tracer trace.Tracer func init() { @@ -83,6 +86,46 @@ const ( ProcessingStatus = "processing:" ) +// Tree is used to manage a tree hierarchy +type Tree interface { + Setup() error + + GetMD(ctx context.Context, node *Node) (os.FileInfo, error) + ListFolder(ctx context.Context, node *Node) ([]*Node, error) + // CreateHome(owner *userpb.UserId) (n *Node, err error) + CreateDir(ctx context.Context, node *Node) (err error) + TouchFile(ctx context.Context, node *Node, markprocessing bool, mtime string) error + // CreateReference(ctx context.Context, node *Node, targetURI *url.URL) error + Move(ctx context.Context, oldNode *Node, newNode *Node) (err error) + Delete(ctx context.Context, node *Node) (err error) + RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPath string, target *Node) (*Node, *Node, func() error, error) + PurgeRecycleItemFunc(ctx context.Context, spaceid, key, purgePath string) (*Node, func() error, error) + + WriteBlob(node *Node, source string) error + ReadBlob(node *Node) (io.ReadCloser, error) + DeleteBlob(node *Node) error + + Propagate(ctx context.Context, node *Node, sizeDiff int64) (err error) +} + +// PathLookup defines the interface for the lookup component +type PathLookup interface { + NodeFromSpaceID(ctx context.Context, spaceID string) (n *Node, err error) + NodeFromResource(ctx context.Context, ref *provider.Reference) (*Node, error) + NodeFromID(ctx context.Context, id *provider.ResourceId) (n *Node, err error) + + GenerateSpaceID(spaceType string, owner *userpb.User) (string, error) + InternalRoot() string + InternalPath(spaceID, nodeID string) string + Path(ctx context.Context, n *Node, hasPermission PermissionFunc) (path string, err error) + MetadataBackend() metadata.Backend + ReadBlobSizeAttr(ctx context.Context, path string) (int64, error) + ReadBlobIDAttr(ctx context.Context, path string) (string, error) + TypeFromPath(ctx context.Context, path string) provider.ResourceType + CopyMetadataWithSourceLock(ctx context.Context, sourcePath, targetPath string, filter func(attributeName string, value []byte) (newValue []byte, copy bool), lockedSource *lockedfile.File, acquireTargetLock bool) (err error) + CopyMetadata(ctx context.Context, src, target string, filter func(attributeName string, value []byte) (newValue []byte, copy bool), acquireTargetLock bool) (err error) +} + // Node represents a node in the tree and provides methods to get a Parent or Child instance type Node struct { SpaceID string @@ -100,16 +143,6 @@ type Node struct { nodeType *provider.ResourceType } -// PathLookup defines the interface for the lookup component -type PathLookup interface { - InternalRoot() string - InternalPath(spaceID, nodeID string) string - Path(ctx context.Context, n *Node, hasPermission PermissionFunc) (path string, err error) - MetadataBackend() metadata.Backend - ReadBlobSizeAttr(ctx context.Context, path string) (int64, error) - ReadBlobIDAttr(ctx context.Context, path string) (string, error) -} - // New returns a new instance of Node func New(spaceID, id, parentID, name string, blobsize int64, blobID string, t provider.ResourceType, owner *userpb.UserId, lu PathLookup) *Node { if blobID == "" { diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spacepermissions.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions/spacepermissions.go similarity index 92% rename from vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spacepermissions.go rename to vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions/spacepermissions.go index 61c42e4bc..7de6144d3 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spacepermissions.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions/spacepermissions.go @@ -1,4 +1,4 @@ -package decomposedfs +package permissions import ( "context" @@ -11,9 +11,27 @@ import ( "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/v2/pkg/utils" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" ) +//go:generate make --no-print-directory -C ../../../../.. mockery NAME=PermissionsChecker +//go:generate make --no-print-directory -C ../../../../.. mockery NAME=CS3PermissionsClient + +var ( + tracer trace.Tracer +) + +func init() { + tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/permissions") +} + +const ( + _spaceTypePersonal = "personal" + _spaceTypeProject = "project" +) + // PermissionsChecker defines an interface for checking permissions on a Node type PermissionsChecker interface { AssemblePermissions(ctx context.Context, n *node.Node) (ap provider.ResourcePermissions, err error) diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaces.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaces.go index a3745492d..0c1f541f8 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaces.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaces.go @@ -44,11 +44,11 @@ import ( "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions" "github.com/cs3org/reva/v2/pkg/storage/utils/filelocks" "github.com/cs3org/reva/v2/pkg/storage/utils/templates" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" - "github.com/google/uuid" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) @@ -66,9 +66,13 @@ const ( // CreateStorageSpace creates a storage space func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { ctx = context.WithValue(ctx, utils.SpaceGrant, struct{}{}) + u := ctxpkg.ContextMustGetUser(ctx) // "everything is a resource" this is the unique ID for the Space resource. - spaceID := uuid.New().String() + spaceID, err := fs.lu.GenerateSpaceID(req.Type, req.GetOwner()) + if err != nil { + return nil, err + } // allow sending a space id if reqSpaceID := utils.ReadPlainFromOpaque(req.Opaque, "spaceid"); reqSpaceID != "" { spaceID = reqSpaceID @@ -77,14 +81,12 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr description := utils.ReadPlainFromOpaque(req.Opaque, "description") // allow sending a spaceAlias alias := utils.ReadPlainFromOpaque(req.Opaque, "spaceAlias") - u := ctxpkg.ContextMustGetUser(ctx) if alias == "" { alias = templates.WithSpacePropertiesAndUser(u, req.Type, req.Name, fs.o.GeneralSpaceAliasTemplate) } // TODO enforce a uuid? // TODO clarify if we want to enforce a single personal storage space or if we want to allow sending the spaceid if req.Type == _spaceTypePersonal { - spaceID = req.GetOwner().GetId().GetOpaqueId() alias = templates.WithSpacePropertiesAndUser(u, req.Type, req.Name, fs.o.PersonalSpaceAliasTemplate) } @@ -620,7 +622,7 @@ func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.Up } - if !restore && len(metadata) == 0 && !IsViewer(sp) { + if !restore && len(metadata) == 0 && !permissions.IsViewer(sp) { // you may land here when making an update request without changes // check if user has access to the drive before continuing return &provider.UpdateStorageSpaceResponse{ @@ -628,10 +630,10 @@ func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.Up }, nil } - if !IsManager(sp) { + if !permissions.IsManager(sp) { // We are not a space manager. We need to check for additional permissions. k := []string{prefixes.NameAttr, prefixes.SpaceDescriptionAttr} - if !IsEditor(sp) { + if !permissions.IsEditor(sp) { k = append(k, prefixes.SpaceReadmeAttr, prefixes.SpaceAliasAttr, prefixes.SpaceImageAttr) } @@ -800,7 +802,7 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, if n.SpaceRoot.IsDisabled(ctx) { rp, err := fs.p.AssemblePermissions(ctx, n) - if err != nil || !IsManager(rp) { + if err != nil || !permissions.IsManager(rp) { return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.ID)) } } @@ -1060,7 +1062,7 @@ func (fs *Decomposedfs) getSpaceRoot(spaceID string) string { // - a user with the "delete-all-spaces" permission may delete but not enable/disable any project space // - a user with the "Drive.ReadWriteEnabled" permission may enable/disable but not delete any project space // - a project space can always be enabled/disabled/deleted by its manager (i.e. users have the "remove" grant) -func canDeleteSpace(ctx context.Context, spaceID string, typ string, purge bool, n *node.Node, p Permissions) error { +func canDeleteSpace(ctx context.Context, spaceID string, typ string, purge bool, n *node.Node, p permissions.Permissions) error { // delete-all-home spaces allows to disable and delete a personal space if typ == "personal" { if p.DeleteAllHomeSpaces(ctx) { @@ -1070,7 +1072,7 @@ func canDeleteSpace(ctx context.Context, spaceID string, typ string, purge bool, } // space managers are allowed to disable and delete their project spaces - if rp, err := p.AssemblePermissions(ctx, n); err == nil && IsManager(rp) { + if rp, err := p.AssemblePermissions(ctx, n); err == nil && permissions.IsManager(rp) { return nil } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/async.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/async.go index 3bd6864d9..2f55b1b99 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/async.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/async.go @@ -48,7 +48,7 @@ type AsyncPropagator struct { treeSizeAccounting bool treeTimeAccounting bool propagationDelay time.Duration - lookup lookup.PathLookup + lookup node.PathLookup } // Change represents a change to the tree @@ -58,7 +58,7 @@ type Change struct { } // NewAsyncPropagator returns a new AsyncPropagator instance -func NewAsyncPropagator(treeSizeAccounting, treeTimeAccounting bool, o options.AsyncPropagatorOptions, lookup lookup.PathLookup) AsyncPropagator { +func NewAsyncPropagator(treeSizeAccounting, treeTimeAccounting bool, o options.AsyncPropagatorOptions, lookup node.PathLookup) AsyncPropagator { p := AsyncPropagator{ treeSizeAccounting: treeSizeAccounting, treeTimeAccounting: treeTimeAccounting, diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/propagator.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/propagator.go index 6f12e43c6..008e8d988 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/propagator.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/propagator.go @@ -26,7 +26,6 @@ import ( sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" @@ -45,7 +44,7 @@ type Propagator interface { Propagate(ctx context.Context, node *node.Node, sizediff int64) error } -func New(lookup lookup.PathLookup, o *options.Options) Propagator { +func New(lookup node.PathLookup, o *options.Options) Propagator { switch o.Propagator { case "async": return NewAsyncPropagator(o.TreeSizeAccounting, o.TreeTimeAccounting, o.AsyncPropagatorOptions, lookup) @@ -54,7 +53,7 @@ func New(lookup lookup.PathLookup, o *options.Options) Propagator { } } -func calculateTreeSize(ctx context.Context, lookup lookup.PathLookup, childrenPath string) (uint64, error) { +func calculateTreeSize(ctx context.Context, lookup node.PathLookup, childrenPath string) (uint64, error) { ctx, span := tracer.Start(ctx, "calculateTreeSize") defer span.End() var size uint64 diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/sync.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/sync.go index 4e03aada6..f3729cb97 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/sync.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/sync.go @@ -26,7 +26,6 @@ import ( "time" "github.com/cs3org/reva/v2/pkg/appctx" - "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" @@ -37,11 +36,11 @@ import ( type SyncPropagator struct { treeSizeAccounting bool treeTimeAccounting bool - lookup lookup.PathLookup + lookup node.PathLookup } // NewSyncPropagator returns a new AsyncPropagator instance -func NewSyncPropagator(treeSizeAccounting, treeTimeAccounting bool, lookup lookup.PathLookup) SyncPropagator { +func NewSyncPropagator(treeSizeAccounting, treeTimeAccounting bool, lookup node.PathLookup) SyncPropagator { return SyncPropagator{ treeSizeAccounting: treeSizeAccounting, treeTimeAccounting: treeTimeAccounting, diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go index dc8643fd2..e6d108c0e 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go @@ -66,7 +66,7 @@ type Blobstore interface { // Tree manages a hierarchical tree type Tree struct { - lookup lookup.PathLookup + lookup node.PathLookup blobstore Blobstore propagator propagator.Propagator @@ -79,7 +79,7 @@ type Tree struct { type PermissionCheckFunc func(rp *provider.ResourcePermissions) bool // New returns a new instance of Tree -func New(lu lookup.PathLookup, bs Blobstore, o *options.Options, cache store.Store) *Tree { +func New(lu node.PathLookup, bs Blobstore, o *options.Options, cache store.Store) *Tree { return &Tree{ lookup: lu, blobstore: bs, diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/store.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/store.go index def5e545f..57a7aea8f 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/store.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload/store.go @@ -53,7 +53,7 @@ type PermissionsChecker interface { // OcisStore manages upload sessions type OcisStore struct { - lu *lookup.Lookup + lu node.PathLookup tp Tree root string pub events.Publisher @@ -62,7 +62,7 @@ type OcisStore struct { } // NewSessionStore returns a new OcisStore -func NewSessionStore(lu *lookup.Lookup, tp Tree, root string, pub events.Publisher, async bool, tknopts options.TokenOptions) *OcisStore { +func NewSessionStore(lu node.PathLookup, tp Tree, root string, pub events.Publisher, async bool, tknopts options.TokenOptions) *OcisStore { return &OcisStore{ lu: lu, tp: tp, diff --git a/vendor/modules.txt b/vendor/modules.txt index fda9b7ba9..4e423ce65 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -362,7 +362,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1 github.com/cs3org/go-cs3apis/cs3/types/v1beta1 -# github.com/cs3org/reva/v2 v2.18.1-0.20240129131717-cff0a2eeb959 => github.com/dragonchaser/reva/v2 v2.4.1-0.20240201130351-4b087c2a6b7b +# github.com/cs3org/reva/v2 v2.18.1-0.20240205065033-2c21ada2ae52 ## explicit; go 1.21 github.com/cs3org/reva/v2/cmd/revad/internal/grace github.com/cs3org/reva/v2/cmd/revad/runtime @@ -658,6 +658,10 @@ github.com/cs3org/reva/v2/pkg/storage/fs/ocis github.com/cs3org/reva/v2/pkg/storage/fs/ocis/blobstore github.com/cs3org/reva/v2/pkg/storage/fs/owncloudsql github.com/cs3org/reva/v2/pkg/storage/fs/owncloudsql/filecache +github.com/cs3org/reva/v2/pkg/storage/fs/posix +github.com/cs3org/reva/v2/pkg/storage/fs/posix/blobstore +github.com/cs3org/reva/v2/pkg/storage/fs/posix/lookup +github.com/cs3org/reva/v2/pkg/storage/fs/posix/tree github.com/cs3org/reva/v2/pkg/storage/fs/registry github.com/cs3org/reva/v2/pkg/storage/fs/s3 github.com/cs3org/reva/v2/pkg/storage/fs/s3ng @@ -670,6 +674,7 @@ github.com/cs3org/reva/v2/pkg/storage/utils/ace github.com/cs3org/reva/v2/pkg/storage/utils/acl github.com/cs3org/reva/v2/pkg/storage/utils/chunking github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs +github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes @@ -677,6 +682,7 @@ github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/migrator github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options +github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaceidindex github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator @@ -2327,4 +2333,3 @@ stash.kopano.io/kgol/rndm # github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/kobergj/plugins/v4/store/nats-js-kv v0.0.0-20231207143248-4d424e3ae348 # github.com/studio-b12/gowebdav => github.com/aduffeck/gowebdav v0.0.0-20231215102054-212d4a4374f6 # github.com/egirna/icap-client => github.com/fschade/icap-client v0.0.0-20240123094924-5af178158eaf -# github.com/cs3org/reva/v2 => github.com/dragonchaser/reva/v2 v2.4.1-0.20240201130351-4b087c2a6b7b From a7f929c6d70973fe76ca1fcb7d9b9e127a614a62 Mon Sep 17 00:00:00 2001 From: Christian Richter <1058116+dragonchaser@users.noreply.github.com> Date: Mon, 5 Feb 2024 11:55:47 +0000 Subject: [PATCH 012/112] Automated changelog update [skip ci] --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42daf8c4c..3b7f5b0e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ The following sections list the changes for unreleased. * Enhancement - Update reva to latest edge version: [#8287](https://github.com/owncloud/ocis/pull/8287) * Enhancement - Allow restarting multiple uploads with one command: [#8287](https://github.com/owncloud/ocis/pull/8287) * Enhancement - Modify the concurrency default: [#8309](https://github.com/owncloud/ocis/pull/8309) +* Enhancement - Bump reva: [#8340](https://github.com/owncloud/ocis/pull/8340) ## Details @@ -303,6 +304,14 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/8309 https://github.com/cs3org/reva/pull/4485 +* Enhancement - Bump reva: [#8340](https://github.com/owncloud/ocis/pull/8340) + + We have bumped reve to pull in the changes needed for automatically increment + filenames on upload collisions in secret filedrops. + + https://github.com/owncloud/ocis/issues/8291 + https://github.com/owncloud/ocis/pull/8340 + # Changelog for [5.0.0-rc.1] (2023-12-27) The following sections list the changes for 5.0.0-rc.1. From 5baeb8893017f52f54f7d0513a11ff2db9f6f0d4 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 5 Feb 2024 17:30:37 +0100 Subject: [PATCH 013/112] Update new-service-checklist.md Fix the port ranges document URL --- docs/services/general-info/new-service-checklist.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/services/general-info/new-service-checklist.md b/docs/services/general-info/new-service-checklist.md index dd50d3f1e..53a908f81 100644 --- a/docs/services/general-info/new-service-checklist.md +++ b/docs/services/general-info/new-service-checklist.md @@ -39,6 +39,6 @@ Use this checklist with copy/paste in your PR - right from the beginning. It ren - [ ] If new envvars are introduced which serve the same purpose but in multiple services, an additional envvar must be added at the beginning of the list starting with `OCIS_` (global envvar). - [ ] Ensure that a service has a debug port - [ ] If the new service introduces a new port: - - the port must be added to [port-ranges.md](https://github.com/owncloud/ocis/blob/master/docs/services/port-ranges.md) and to the readme.md file. + - the port must be added to [port-ranges.md](https://github.com/owncloud/ocis/blob/master/docs/services/general-info/port-ranges.md) and to the readme.md file. - [ ] Make sure to have a function `FullDefaultConfig()` in `pkg/config/defaults/defaultconfig.go` of your service. It is needed to create the documentation. ``` From 1eb063e676be425b48c1215949326785081e17ca Mon Sep 17 00:00:00 2001 From: Sabin Panta <64484313+S-Panta@users.noreply.github.com> Date: Tue, 6 Feb 2024 13:04:04 +0545 Subject: [PATCH 014/112] removed datatable for resource type row (#8369) --- .../apiSharingNg/shareInvitations.feature | 291 ++++++++---------- .../features/apiSharingNg/sharedByMe.feature | 14 +- 2 files changed, 134 insertions(+), 171 deletions(-) diff --git a/tests/acceptance/features/apiSharingNg/shareInvitations.feature b/tests/acceptance/features/apiSharingNg/shareInvitations.feature index 2e2af55dc..aac62a8c6 100644 --- a/tests/acceptance/features/apiSharingNg/shareInvitations.feature +++ b/tests/acceptance/features/apiSharingNg/shareInvitations.feature @@ -16,7 +16,6 @@ Feature: Send a sharing invitations Given user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | Brian | @@ -88,12 +87,12 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: send share invitation to group with different roles @@ -106,7 +105,6 @@ Feature: Send a sharing invitations And user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | grp1 | @@ -180,18 +178,17 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: send share invitation for a file to user with different permissions Given user "Alice" has uploaded file with content "to share" to "textfile1.txt" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | sharee | Brian | @@ -283,7 +280,6 @@ Feature: Send a sharing invitations Scenario Outline: send share invitation for a folder to user with different permissions Given user "Alice" has created folder "FolderToShare" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | folder | | resource | FolderToShare | | space | Personal | | sharee | Brian | @@ -384,7 +380,6 @@ Feature: Send a sharing invitations | Carol | grp1 | And user "Alice" has uploaded file with content "to share" to "textfile1.txt" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | sharee | grp1 | @@ -482,7 +477,6 @@ Feature: Send a sharing invitations | Carol | grp1 | And user "Alice" has created folder "FolderToShare" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | folder | | resource | FolderToShare | | space | Personal | | sharee | grp1 | @@ -578,7 +572,6 @@ Feature: Send a sharing invitations Given user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | Brian | @@ -656,12 +649,12 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: send share invitation with expiration date to group with different roles @@ -674,7 +667,6 @@ Feature: Send a sharing invitations And user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | grp1 | @@ -752,12 +744,12 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | @issue-7962 Scenario Outline: send share invitation to disabled user @@ -765,7 +757,6 @@ Feature: Send a sharing invitations And user "Alice" has created folder "FolderToShare" And the user "Admin" has disabled user "Brian" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | Brian | @@ -835,12 +826,12 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: send sharing invitation to a deleted group with different roles @@ -854,7 +845,6 @@ Feature: Send a sharing invitations And user "Alice" has created folder "FolderToShare" And the administrator has deleted group "grp1" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | grp1 | @@ -894,12 +884,12 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: send share invitation to deleted user @@ -907,7 +897,6 @@ Feature: Send a sharing invitations And user "Alice" has created folder "FolderToShare" And the user "Admin" has deleted a user "Brian" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | Brian | @@ -945,12 +934,12 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: try to send sharing invitation to multiple groups @@ -968,7 +957,6 @@ Feature: Send a sharing invitations And user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | grp1, grp2 | @@ -1008,14 +996,14 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Co Owner | folder | FolderToShare | - | Uploader | folder | FolderToShare | - | Manager | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Co Owner | FolderToShare | + | Uploader | FolderToShare | + | Manager | FolderToShare | Scenario Outline: try to send sharing invitation to user and group at once @@ -1031,7 +1019,6 @@ Feature: Send a sharing invitations And user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | grp1, Bob | @@ -1071,19 +1058,18 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: send sharing invitation to non-existing group Given user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | nonExistentGroup | @@ -1123,12 +1109,12 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: send sharing invitation to already shared group @@ -1141,14 +1127,12 @@ Feature: Send a sharing invitations And user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" And user "Alice" has sent the following share invitation: - | resourceType | | | resource | | | space | Personal | | sharee | grp1 | | shareType | group | | permissionsRole | | When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | grp1 | @@ -1186,19 +1170,18 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Viewer | file | /textfile1.txt | - | File Editor | file | /textfile1.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissions-role | path | + | Viewer | /textfile1.txt | + | File Editor | /textfile1.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: send share invitation to wrong user id Given user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" tries to send the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | shareeId | a4c0c83e-ae24-4870-93c3-fcaf2a2228f7 | @@ -1236,21 +1219,20 @@ Feature: Send a sharing invitations } """ Examples: - | resource-type | path | - | file | /textfile1.txt | - | folder | FolderToShare | + | path | + | /textfile1.txt | + | FolderToShare | Scenario Outline: send share invitation with empty user id Given user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" tries to send the following share invitation using the Graph API: - | resourceType | | - | resource | | - | space | Personal | - | shareeId | | - | shareType | user | - | permissionsRole | Viewer | + | resource | | + | space | Personal | + | shareeId | | + | shareType | user | + | permissionsRole | Viewer | Then the HTTP status code should be "400" And the JSON data of the response should match """ @@ -1283,21 +1265,20 @@ Feature: Send a sharing invitations } """ Examples: - | resource-type | path | - | file | /textfile1.txt | - | folder | FolderToShare | + | path | + | /textfile1.txt | + | FolderToShare | Scenario Outline: send share invitation to user with wrong recipient type Given user "Alice" has uploaded file with content "to share" to "textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" tries to send the following share invitation using the Graph API: - | resourceType | | - | resource | | - | space | Personal | - | sharee | Brian | - | shareType | wrongShareType | - | permissionsRole | Viewer | + | resource | | + | space | Personal | + | sharee | Brian | + | shareType | wrongShareType | + | permissionsRole | Viewer | Then the HTTP status code should be "400" And the JSON data of the response should match """ @@ -1330,9 +1311,9 @@ Feature: Send a sharing invitations } """ Examples: - | resource-type | path | - | file | /textfile1.txt | - | folder | FolderToShare | + | path | + | /textfile1.txt | + | FolderToShare | Scenario Outline: send share invitation to group with wrong recipient type @@ -1345,12 +1326,11 @@ Feature: Send a sharing invitations | Brian | grp1 | | Carol | grp1 | When user "Alice" tries to send the following share invitation using the Graph API: - | resourceType | | - | resource | | - | space | Personal | - | sharee | grp1 | - | shareType | wrongShareType | - | permissionsRole | Viewer | + | resource | | + | space | Personal | + | sharee | grp1 | + | shareType | wrongShareType | + | permissionsRole | Viewer | Then the HTTP status code should be "400" And the JSON data of the response should match """ @@ -1383,21 +1363,20 @@ Feature: Send a sharing invitations } """ Examples: - | resource-type | path | - | file | /textfile1.txt | - | folder | FolderToShare | + | path | + | /textfile1.txt | + | FolderToShare | Scenario Outline: send share invitation to user with empty recipient type Given user "Alice" has uploaded file with content "to share" to "textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" tries to send the following share invitation using the Graph API: - | resourceType | | - | resource | | - | space | Personal | - | sharee | Brian | - | shareType | | - | permissionsRole | Viewer | + | resource | | + | space | Personal | + | sharee | Brian | + | shareType | | + | permissionsRole | Viewer | Then the HTTP status code should be "400" And the JSON data of the response should match """ @@ -1430,9 +1409,9 @@ Feature: Send a sharing invitations } """ Examples: - | resource-type | path | - | file | /textfile1.txt | - | folder | FolderToShare | + | path | + | /textfile1.txt | + | FolderToShare | Scenario Outline: send share invitation to group with empty recipient type @@ -1445,12 +1424,11 @@ Feature: Send a sharing invitations | Brian | grp1 | | Carol | grp1 | When user "Alice" tries to send the following share invitation using the Graph API: - | resourceType | | - | resource | | - | space | Personal | - | sharee | grp1 | - | shareType | | - | permissionsRole | Viewer | + | resource | | + | space | Personal | + | sharee | grp1 | + | shareType | | + | permissionsRole | Viewer | Then the HTTP status code should be "400" And the JSON data of the response should match """ @@ -1483,16 +1461,15 @@ Feature: Send a sharing invitations } """ Examples: - | resource-type | path | - | file | /textfile1.txt | - | folder | FolderToShare | + | path | + | /textfile1.txt | + | FolderToShare | Scenario Outline: try to share a resource with invalid roles Given user "Alice" has uploaded file with content "to share" to "/textfile1.txt" And user "Alice" has created folder "FolderToShare" When user "Alice" sends the following share invitation using the Graph API: - | resourceType | | | resource | | | space | Personal | | sharee | Brian | @@ -1532,15 +1509,15 @@ Feature: Send a sharing invitations } """ Examples: - | permissions-role | resource-type | path | - | Co Owner | file | /textfile1.txt | - | Manager | file | /textfile1.txt | - | Space Viewer | file | /textfile1.txt | - | Space Editor | file | /textfile1.txt | - | Co Owner | folder | FolderToShare | - | Manager | folder | FolderToShare | - | Space Viewer | folder | FolderToShare | - | Space Editor | folder | FolderToShare | + | permissions-role | path | + | Co Owner | /textfile1.txt | + | Manager | /textfile1.txt | + | Space Viewer | /textfile1.txt | + | Space Editor | /textfile1.txt | + | Co Owner | FolderToShare | + | Manager | FolderToShare | + | Space Viewer | FolderToShare | + | Space Editor | FolderToShare | Scenario Outline: try to share a file with invalid roles @@ -1594,19 +1571,17 @@ Feature: Send a sharing invitations Given user "Alice" has uploaded file with content "to share" to "textfile1.txt" And user "Alice" has created folder "FolderToShare" And user "Alice" has sent the following share invitation: - | resourceType | | - | resource | | - | space | Personal | - | sharee | Brian | - | shareType | user | - | permissionsRole | Viewer | + | resource | | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | When user "Alice" tries to send the following share invitation using the Graph API: - | resourceType | | - | resource | | - | space | Personal | - | sharee | Brian | - | shareType | user | - | permissionsRole | Viewer | + | resource | | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | Then the HTTP status code should be "409" And the JSON data of the response should match """ @@ -1637,9 +1612,9 @@ Feature: Send a sharing invitations } """ Examples: - | resource-type | path | - | file | /textfile1.txt | - | folder | FolderToShare | + | path | + | /textfile1.txt | + | FolderToShare | Scenario Outline: send share invitation for project space to user with different roles diff --git a/tests/acceptance/features/apiSharingNg/sharedByMe.feature b/tests/acceptance/features/apiSharingNg/sharedByMe.feature index ecdd5a34e..b9f083676 100644 --- a/tests/acceptance/features/apiSharingNg/sharedByMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedByMe.feature @@ -15,7 +15,6 @@ Feature: resources shared by user Scenario: user lists the shared file from personal space Given user "Alice" has uploaded file with content "hello world" to "textfile.txt" And user "Alice" has sent the following share invitation: - | resourceType | file | | resource | textfile.txt | | space | Personal | | sharee | Brian | @@ -140,7 +139,6 @@ Feature: resources shared by user Given user "Alice" has created folder "FolderToShare" And user "Alice" has uploaded file with content "hello world" to "FolderToShare/textfile.txt" And user "Alice" has sent the following share invitation: - | resourceType | file | | resource | FolderToShare/textfile.txt | | space | Personal | | sharee | Brian | @@ -264,7 +262,6 @@ Feature: resources shared by user Scenario: user lists the shared folder from personal space Given user "Alice" has created folder "FolderToShare" And user "Alice" has sent the following share invitation: - | resourceType | folder | | resource | FolderToShare | | space | Personal | | sharee | Brian | @@ -382,14 +379,12 @@ Feature: resources shared by user Given user "Alice" has created folder "FolderToShare" And user "Alice" has uploaded file with content "hello world" to "textfile.txt" And user "Alice" has sent the following share invitation: - | resourceType | file | | resource | textfile.txt | | space | Personal | | sharee | Brian | | shareType | user | | permissionsRole | Viewer | And user "Alice" has sent the following share invitation: - | resourceType | folder | | resource | FolderToShare | | space | Personal | | sharee | Brian | @@ -620,7 +615,6 @@ Feature: resources shared by user And user "Alice" has created a space "NewSpace" with the default quota using the Graph API And user "Alice" has uploaded a file inside space "NewSpace" with content "hello world" to "textfile.txt" And user "Alice" has sent the following share invitation: - | resourceType | file | | resource | textfile.txt | | space | NewSpace | | sharee | Brian | @@ -748,7 +742,6 @@ Feature: resources shared by user And user "Alice" has created a folder "FolderToShare" in space "NewSpace" And user "Alice" has uploaded a file inside space "NewSpace" with content "hello world" to "FolderToShare/textfile.txt" And user "Alice" has sent the following share invitation: - | resourceType | file | | resource | FolderToShare/textfile.txt | | space | NewSpace | | sharee | Brian | @@ -875,7 +868,6 @@ Feature: resources shared by user And user "Alice" has created a space "NewSpace" with the default quota using the Graph API And user "Alice" has created a folder "FolderToShare" in space "NewSpace" And user "Alice" has sent the following share invitation: - | resourceType | folder | | resource | FolderToShare | | space | NewSpace | | sharee | Brian | @@ -996,14 +988,12 @@ Feature: resources shared by user And user "Alice" has created a folder "FolderToShare" in space "NewSpace" And user "Alice" has uploaded a file inside space "NewSpace" with content "hello world" to "textfile.txt" And user "Alice" has sent the following share invitation: - | resourceType | file | | resource | textfile.txt | | space | NewSpace | | sharee | Brian | | shareType | user | | permissionsRole | Viewer | And user "Alice" has sent the following share invitation: - | resourceType | folder | | resource | FolderToShare | | space | NewSpace | | sharee | Brian | @@ -1233,14 +1223,12 @@ Feature: resources shared by user And user "Alice" has created folder "FolderToShare" And user "Alice" has uploaded file with content "hello world" to "textfile.txt" And user "Alice" has sent the following share invitation: - | resourceType | file | | resource | textfile.txt | | space | Personal | | sharee | grp1 | | shareType | group | | permissionsRole | Viewer | And user "Alice" has sent the following share invitation: - | resourceType | folder | | resource | FolderToShare | | space | Personal | | sharee | grp1 | @@ -1533,4 +1521,4 @@ Feature: resources shared by user } } } - """ \ No newline at end of file + """ From 14888f3b7c44f27d7b0835c3c516d8fae4f5437b Mon Sep 17 00:00:00 2001 From: Prarup Gurung Date: Tue, 6 Feb 2024 12:13:54 +0545 Subject: [PATCH 015/112] Added tests to lists file shared with them when sync is disabled --- tests/acceptance/config/behat.yml | 1 + .../apiSharingNg/sharedWithMe.feature | 268 ++++++++++++++++++ .../features/bootstrap/SettingsContext.php | 1 + 3 files changed, 270 insertions(+) diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml index 0420c0ff5..d05231870 100644 --- a/tests/acceptance/config/behat.yml +++ b/tests/acceptance/config/behat.yml @@ -316,6 +316,7 @@ default: - SharingNgContext: - PublicWebDavContext: - OcisConfigContext: + - SettingsContext: extensions: rdx\behatvars\BehatVariablesExtension: ~ diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature index 82b8a2ee0..d5741e2e5 100644 --- a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature @@ -593,3 +593,271 @@ Feature: an user gets the resources shared to them } } """ + + + Scenario: user lists the file shared with them when sync is disabled + Given user "Alice" has uploaded file with content "to share" to "textfile.txt" + And user "Brian" has disabled sync + And user "Alice" has sent the following share invitation: + | resource | textfile.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem", + "size" + ], + "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [false] + }, + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile.txt"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType", "id"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions", + "size" + ], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile.txt"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + ] + }, + "size": { + "type": "number", + "enum": [8] + } + } + }, + "size": { + "type": "number", + "enum": [8] + } + } + } + } + } + } + """ diff --git a/tests/acceptance/features/bootstrap/SettingsContext.php b/tests/acceptance/features/bootstrap/SettingsContext.php index 90d31ef4f..fefdf8f92 100644 --- a/tests/acceptance/features/bootstrap/SettingsContext.php +++ b/tests/acceptance/features/bootstrap/SettingsContext.php @@ -523,6 +523,7 @@ class SettingsContext implements Context { /** * @Given user :user has disabled auto-accepting + * @Given user :user has disabled sync * * @param string $user * From 9bb711acb9c7b7f3c0009e63fa884ec299a32aae Mon Sep 17 00:00:00 2001 From: Prarup Gurung Date: Tue, 6 Feb 2024 12:42:09 +0545 Subject: [PATCH 016/112] Added tests to lists folder shared with them when sync is disabled --- .../apiSharingNg/sharedWithMe.feature | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature index d5741e2e5..53c6b74a5 100644 --- a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature @@ -861,3 +861,241 @@ Feature: an user gets the resources shared to them } } """ + + + Scenario: user lists the folder shared with them when sync is disabled + Given user "Alice" has created folder "folderToShare" + And user "Brian" has disabled sync + And user "Alice" has sent the following share invitation: + | resource | folderToShare | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem" + ], + "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [false] + }, + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folderToShare"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType", "id"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions" + ], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folderToShare"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + ] + } + } + } + } + } + } + } + } + """ From f7eb4db082e5ecb53cf87a7cbdd911efe37dad1f Mon Sep 17 00:00:00 2001 From: Prarup Gurung Date: Tue, 6 Feb 2024 14:36:45 +0545 Subject: [PATCH 017/112] Addressed reviews --- .../acceptance/features/apiSharingNg/sharedWithMe.feature | 8 ++++---- tests/acceptance/features/bootstrap/SettingsContext.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature index 53c6b74a5..c4d249487 100644 --- a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature @@ -595,9 +595,9 @@ Feature: an user gets the resources shared to them """ - Scenario: user lists the file shared with them when sync is disabled + Scenario: user lists the file shared with them when auto-sync is disabled Given user "Alice" has uploaded file with content "to share" to "textfile.txt" - And user "Brian" has disabled sync + And user "Brian" has disabled the auto-sync share And user "Alice" has sent the following share invitation: | resource | textfile.txt | | space | Personal | @@ -863,9 +863,9 @@ Feature: an user gets the resources shared to them """ - Scenario: user lists the folder shared with them when sync is disabled + Scenario: user lists the folder shared with them when auto-sync is disabled Given user "Alice" has created folder "folderToShare" - And user "Brian" has disabled sync + And user "Brian" has disabled the auto-sync share And user "Alice" has sent the following share invitation: | resource | folderToShare | | space | Personal | diff --git a/tests/acceptance/features/bootstrap/SettingsContext.php b/tests/acceptance/features/bootstrap/SettingsContext.php index fefdf8f92..c099a131b 100644 --- a/tests/acceptance/features/bootstrap/SettingsContext.php +++ b/tests/acceptance/features/bootstrap/SettingsContext.php @@ -523,7 +523,7 @@ class SettingsContext implements Context { /** * @Given user :user has disabled auto-accepting - * @Given user :user has disabled sync + * @Given user :user has disabled the auto-sync share * * @param string $user * From 8340567db504c206b3ec472a1c291f218536bc92 Mon Sep 17 00:00:00 2001 From: Prarup Gurung Date: Tue, 6 Feb 2024 14:52:45 +0545 Subject: [PATCH 018/112] Added tests to lists file shared with group when sync is disabled --- .../apiSharingNg/sharedWithMe.feature | 274 ++++++++++++++++++ 1 file changed, 274 insertions(+) diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature index c4d249487..1750d7625 100644 --- a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature @@ -1099,3 +1099,277 @@ Feature: an user gets the resources shared to them } } """ + + + Scenario: group member lists the file shared with them when auto-sync is disabled + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And the following users have been added to the following groups + | username | groupname | + | Brian | grp1 | + | Carol | grp1 | + Given user "Alice" has uploaded file with content "to share" to "textfile.txt" + And user "Brian" has disabled the auto-sync share + And user "Alice" has sent the following share invitation: + | resource | textfile.txt | + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem", + "size" + ], + "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [false] + }, + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile.txt"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType", "id"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions", + "size" + ], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile.txt"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["group"], + "properties": { + "group": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%group_id_pattern%$" + } + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + ] + }, + "size": { + "type": "number", + "enum": [8] + } + } + }, + "size": { + "type": "number", + "enum": [8] + } + } + } + } + } + } + """ From c4a5f6a276702039048456f6fe5a8f28e7c16b40 Mon Sep 17 00:00:00 2001 From: Prarup Gurung Date: Tue, 6 Feb 2024 14:53:19 +0545 Subject: [PATCH 019/112] Added tests to lists folder shared with group when sync is disabled --- .../apiSharingNg/sharedWithMe.feature | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature index 1750d7625..c013a7f1a 100644 --- a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature @@ -1373,3 +1373,247 @@ Feature: an user gets the resources shared to them } } """ + + + Scenario: group member lists the folder shared with them when auto-sync is disabled + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And the following users have been added to the following groups + | username | groupname | + | Brian | grp1 | + | Carol | grp1 | + Given user "Alice" has created folder "folderToShare" + And user "Brian" has disabled the auto-sync share + And user "Alice" has sent the following share invitation: + | resource | folderToShare | + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem" + ], + "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [false] + }, + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folderToShare"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType", "id"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions" + ], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folderToShare"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["group"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%group_id_pattern%$" + } + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + ] + } + } + } + } + } + } + } + } + """ From 6e721eb4f5598a2dcf0097f1e71616c812eef4df Mon Sep 17 00:00:00 2001 From: mmattel Date: Tue, 6 Feb 2024 11:52:10 +0100 Subject: [PATCH 020/112] [docs-only] Fix envvar descriptions trailing a double dot --- services/audit/pkg/config/config.go | 6 +++--- services/clientlog/pkg/config/config.go | 6 +++--- services/eventhistory/pkg/config/config.go | 6 +++--- services/frontend/pkg/config/config.go | 6 +++--- services/graph/pkg/config/config.go | 6 +++--- services/nats/pkg/config/config.go | 2 +- services/notifications/pkg/config/config.go | 6 +++--- services/sharing/pkg/config/config.go | 2 +- services/storage-users/pkg/config/config.go | 6 +++--- services/userlog/pkg/config/config.go | 6 +++--- 10 files changed, 26 insertions(+), 26 deletions(-) diff --git a/services/audit/pkg/config/config.go b/services/audit/pkg/config/config.go index 0ee9e5493..f0ea50bb9 100644 --- a/services/audit/pkg/config/config.go +++ b/services/audit/pkg/config/config.go @@ -28,9 +28,9 @@ type Events struct { Cluster string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;AUDIT_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."` TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;AUDIT_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."` TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;AUDIT_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided AUDIT_EVENTS_TLS_INSECURE will be seen as false."` - EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;AUDIT_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;AUDIT_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;AUDIT_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;AUDIT_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;AUDIT_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;AUDIT_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` } // Auditlog holds audit log information diff --git a/services/clientlog/pkg/config/config.go b/services/clientlog/pkg/config/config.go index 921dc2737..aa1d8b12b 100644 --- a/services/clientlog/pkg/config/config.go +++ b/services/clientlog/pkg/config/config.go @@ -34,9 +34,9 @@ type Events struct { Cluster string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;CLIENTLOG_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."` TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;CLIENTLOG_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."` TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;CLIENTLOG_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false."` - EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;CLIENTLOG_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;CLIENTLOG_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;CLIENTLOG_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;CLIENTLOG_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;CLIENTLOG_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;CLIENTLOG_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` } // TokenManager is the config for using the reva token manager diff --git a/services/eventhistory/pkg/config/config.go b/services/eventhistory/pkg/config/config.go index 84ffa4506..15c6229d5 100644 --- a/services/eventhistory/pkg/config/config.go +++ b/services/eventhistory/pkg/config/config.go @@ -53,7 +53,7 @@ type Events struct { Cluster string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;EVENTHISTORY_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."` TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;EVENTHISTORY_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."` TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;EVENTHISTORY_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. Will be seen as empty if NOTIFICATIONS_EVENTS_TLS_INSECURE is provided."` - EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;EVENTHISTORY_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;EVENTHISTORY_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;EVENTHISTORY_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;EVENTHISTORY_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;EVENTHISTORY_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;EVENTHISTORY_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` } diff --git a/services/frontend/pkg/config/config.go b/services/frontend/pkg/config/config.go index 2c2aea74c..f620c6a5f 100644 --- a/services/frontend/pkg/config/config.go +++ b/services/frontend/pkg/config/config.go @@ -171,9 +171,9 @@ type Events struct { Cluster string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;FRONTEND_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."` TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;FRONTEND_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."` TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"FRONTEND_EVENTS_TLS_ROOT_CA_CERTIFICATE;OCS_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false."` - EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;FRONTEND_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;FRONTEND_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;FRONTEND_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;FRONTEND_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;FRONTEND_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;FRONTEND_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` } // ServiceAccount is the configuration for the used service account diff --git a/services/graph/pkg/config/config.go b/services/graph/pkg/config/config.go index 3f2212270..7bfbfd3cd 100644 --- a/services/graph/pkg/config/config.go +++ b/services/graph/pkg/config/config.go @@ -120,9 +120,9 @@ type Events struct { Cluster string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;GRAPH_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture."` TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;GRAPH_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."` TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;GRAPH_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided GRAPH_EVENTS_TLS_INSECURE will be seen as false."` - EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;GRAPH_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;GRAPH_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;GRAPH_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;GRAPH_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;GRAPH_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;GRAPH_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` } // CORS defines the available cors configuration. diff --git a/services/nats/pkg/config/config.go b/services/nats/pkg/config/config.go index 47427b22f..7d3d83aa6 100644 --- a/services/nats/pkg/config/config.go +++ b/services/nats/pkg/config/config.go @@ -28,7 +28,7 @@ type Nats struct { TLSCert string `yaml:"tls_cert" env:"NATS_TLS_CERT" desc:"Path/File name of the TLS server certificate (in PEM format) for the NATS listener. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/nats."` TLSKey string `yaml:"tls_key" env:"NATS_TLS_KEY" desc:"Path/File name for the TLS certificate key (in PEM format) for the NATS listener. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/nats."` TLSSkipVerifyClientCert bool `yaml:"tls_skip_verify_client_cert" env:"OCIS_INSECURE;NATS_TLS_SKIP_VERIFY_CLIENT_CERT" desc:"Whether the NATS server should skip the client certificate verification during the TLS handshake."` - EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;NATS_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;NATS_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services."` } // Tracing is the tracing config diff --git a/services/notifications/pkg/config/config.go b/services/notifications/pkg/config/config.go index 8ea2ec174..75cea110c 100644 --- a/services/notifications/pkg/config/config.go +++ b/services/notifications/pkg/config/config.go @@ -55,9 +55,9 @@ type Events struct { Cluster string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;NOTIFICATIONS_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."` TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;NOTIFICATIONS_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."` TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;NOTIFICATIONS_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false."` - EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;NOTIFICATIONS_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;NOTIFICATIONS_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;NOTIFICATIONS_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;NOTIFICATIONS_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;NOTIFICATIONS_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;NOTIFICATIONS_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` } // ServiceAccount is the configuration for the used service account diff --git a/services/sharing/pkg/config/config.go b/services/sharing/pkg/config/config.go index 7d51d0b83..f7fc47b6d 100644 --- a/services/sharing/pkg/config/config.go +++ b/services/sharing/pkg/config/config.go @@ -152,7 +152,7 @@ type Events struct { ClusterID string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;SHARING_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."` TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;SHARING_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."` TLSRootCaCertPath string `yaml:"tls_root_ca_cert_path" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided SHARING_EVENTS_TLS_INSECURE will be seen as false."` - EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;SHARING_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;SHARING_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services."` AuthUsername string `yaml:"auth_username" env:"OCIS_EVENTS_AUTH_USERNAME;SHARING_EVENTS_AUTH_USERNAME" desc:"Username for the events broker."` AuthPassword string `yaml:"auth_password" env:"OCIS_EVENTS_AUTH_PASSWORD;SHARING_EVENTS_AUTH_PASSWORD" desc:"Password for the events broker."` } diff --git a/services/storage-users/pkg/config/config.go b/services/storage-users/pkg/config/config.go index 8fd16c74b..43cdaa61d 100644 --- a/services/storage-users/pkg/config/config.go +++ b/services/storage-users/pkg/config/config.go @@ -175,10 +175,10 @@ type Events struct { ClusterID string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;STORAGE_USERS_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."` TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;STORAGE_USERS_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."` TLSRootCaCertPath string `yaml:"tls_root_ca_cert_path" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;STORAGE_USERS_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided STORAGE_USERS_EVENTS_TLS_INSECURE will be seen as false."` - EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;STORAGE_USERS_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;STORAGE_USERS_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services."` NumConsumers int `yaml:"num_consumers" env:"STORAGE_USERS_EVENTS_NUM_CONSUMERS" desc:"The amount of concurrent event consumers to start. Event consumers are used for post-processing files. Multiple consumers increase parallelisation, but will also increase CPU and memory demands. The setting has no effect when the OCIS_ASYNC_UPLOADS is set to false. The default and minimum value is 1."` - AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;STORAGE_USERS_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;STORAGE_USERS_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;STORAGE_USERS_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;STORAGE_USERS_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` } // FilemetadataCache holds cache config diff --git a/services/userlog/pkg/config/config.go b/services/userlog/pkg/config/config.go index 9afde9a3c..f6b31563e 100644 --- a/services/userlog/pkg/config/config.go +++ b/services/userlog/pkg/config/config.go @@ -55,9 +55,9 @@ type Events struct { Cluster string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;USERLOG_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."` TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;USERLOG_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."` TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;USERLOG_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false."` - EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;USERLOG_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;USERLOG_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` - AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;USERLOG_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.."` + EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;USERLOG_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;USERLOG_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` + AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;USERLOG_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services."` } // CORS defines the available cors configuration. From 42ade74c718edab07f8b846ef27061b34542c73c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 06:53:52 +0000 Subject: [PATCH 021/112] build(deps): bump github.com/rs/zerolog from 1.31.0 to 1.32.0 Bumps [github.com/rs/zerolog](https://github.com/rs/zerolog) from 1.31.0 to 1.32.0. - [Release notes](https://github.com/rs/zerolog/releases) - [Commits](https://github.com/rs/zerolog/compare/v1.31.0...v1.32.0) --- updated-dependencies: - dependency-name: github.com/rs/zerolog dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/rs/zerolog/README.md | 2 +- vendor/github.com/rs/zerolog/console.go | 66 +++++---- vendor/github.com/rs/zerolog/context.go | 33 ++++- vendor/github.com/rs/zerolog/event.go | 2 +- vendor/github.com/rs/zerolog/example.jsonl | 7 + vendor/github.com/rs/zerolog/fields.go | 23 ++- vendor/github.com/rs/zerolog/globals.go | 28 ++++ vendor/github.com/rs/zerolog/log.go | 32 +++- vendor/github.com/rs/zerolog/pretty.png | Bin 84064 -> 118839 bytes vendor/github.com/rs/zerolog/syslog.go | 9 ++ vendor/github.com/rs/zerolog/writer.go | 164 +++++++++++++++++++++ vendor/modules.txt | 2 +- 14 files changed, 328 insertions(+), 46 deletions(-) create mode 100644 vendor/github.com/rs/zerolog/example.jsonl diff --git a/go.mod b/go.mod index 310ea735f..7ba7837ae 100644 --- a/go.mod +++ b/go.mod @@ -75,7 +75,7 @@ require ( github.com/r3labs/sse/v2 v2.10.0 github.com/riandyrn/otelchi v0.5.1 github.com/rogpeppe/go-internal v1.12.0 - github.com/rs/zerolog v1.31.0 + github.com/rs/zerolog v1.32.0 github.com/shamaton/msgpack/v2 v2.1.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index 7c193d7f3..80a34ec6a 100644 --- a/go.sum +++ b/go.sum @@ -1915,8 +1915,8 @@ github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys= github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/vendor/github.com/rs/zerolog/README.md b/vendor/github.com/rs/zerolog/README.md index 972b729fb..afa615693 100644 --- a/vendor/github.com/rs/zerolog/README.md +++ b/vendor/github.com/rs/zerolog/README.md @@ -547,7 +547,7 @@ and facilitates the unification of logging and tracing in some systems: type TracingHook struct{} func (h TracingHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { - ctx := e.Ctx() + ctx := e.GetCtx() spanId := getSpanIdFromContext(ctx) // as per your tracing framework e.Str("span-id", spanId) } diff --git a/vendor/github.com/rs/zerolog/console.go b/vendor/github.com/rs/zerolog/console.go index 282798853..cc6d623e2 100644 --- a/vendor/github.com/rs/zerolog/console.go +++ b/vendor/github.com/rs/zerolog/console.go @@ -76,6 +76,8 @@ type ConsoleWriter struct { FormatErrFieldValue Formatter FormatExtra func(map[string]interface{}, *bytes.Buffer) error + + FormatPrepare func(map[string]interface{}) error } // NewConsoleWriter creates and initializes a new ConsoleWriter. @@ -124,6 +126,13 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) { return n, fmt.Errorf("cannot decode event: %s", err) } + if w.FormatPrepare != nil { + err = w.FormatPrepare(evt) + if err != nil { + return n, err + } + } + for _, p := range w.PartsOrder { w.writePart(buf, evt, p) } @@ -146,6 +155,15 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) { return len(p), err } +// Call the underlying writer's Close method if it is an io.Closer. Otherwise +// does nothing. +func (w ConsoleWriter) Close() error { + if closer, ok := w.Out.(io.Closer); ok { + return closer.Close() + } + return nil +} + // writeFields appends formatted key-value pairs to buf. func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer) { var fields = make([]string, 0, len(evt)) @@ -272,7 +290,7 @@ func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, } case MessageFieldName: if w.FormatMessage == nil { - f = consoleDefaultFormatMessage + f = consoleDefaultFormatMessage(w.NoColor, evt[LevelFieldName]) } else { f = w.FormatMessage } @@ -310,10 +328,10 @@ func needsQuote(s string) bool { return false } -// colorize returns the string s wrapped in ANSI code c, unless disabled is true. +// colorize returns the string s wrapped in ANSI code c, unless disabled is true or c is 0. func colorize(s interface{}, c int, disabled bool) string { e := os.Getenv("NO_COLOR") - if e != "" { + if e != "" || c == 0 { disabled = true } @@ -378,27 +396,16 @@ func consoleDefaultFormatLevel(noColor bool) Formatter { return func(i interface{}) string { var l string if ll, ok := i.(string); ok { - switch ll { - case LevelTraceValue: - l = colorize("TRC", colorMagenta, noColor) - case LevelDebugValue: - l = colorize("DBG", colorYellow, noColor) - case LevelInfoValue: - l = colorize("INF", colorGreen, noColor) - case LevelWarnValue: - l = colorize("WRN", colorRed, noColor) - case LevelErrorValue: - l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor) - case LevelFatalValue: - l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor) - case LevelPanicValue: - l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor) - default: - l = colorize(ll, colorBold, noColor) + level, _ := ParseLevel(ll) + fl, ok := FormattedLevels[level] + if ok { + l = colorize(fl, LevelColors[level], noColor) + } else { + l = strings.ToUpper(ll)[0:3] } } else { if i == nil { - l = colorize("???", colorBold, noColor) + l = "???" } else { l = strings.ToUpper(fmt.Sprintf("%s", i))[0:3] } @@ -425,11 +432,18 @@ func consoleDefaultFormatCaller(noColor bool) Formatter { } } -func consoleDefaultFormatMessage(i interface{}) string { - if i == nil { - return "" +func consoleDefaultFormatMessage(noColor bool, level interface{}) Formatter { + return func(i interface{}) string { + if i == nil || i == "" { + return "" + } + switch level { + case LevelInfoValue, LevelWarnValue, LevelErrorValue, LevelFatalValue, LevelPanicValue: + return colorize(fmt.Sprintf("%s", i), colorBold, noColor) + default: + return fmt.Sprintf("%s", i) + } } - return fmt.Sprintf("%s", i) } func consoleDefaultFormatFieldName(noColor bool) Formatter { @@ -450,6 +464,6 @@ func consoleDefaultFormatErrFieldName(noColor bool) Formatter { func consoleDefaultFormatErrFieldValue(noColor bool) Formatter { return func(i interface{}) string { - return colorize(fmt.Sprintf("%s", i), colorRed, noColor) + return colorize(colorize(fmt.Sprintf("%s", i), colorBold, noColor), colorRed, noColor) } } diff --git a/vendor/github.com/rs/zerolog/context.go b/vendor/github.com/rs/zerolog/context.go index fc62ad9c1..df352eb66 100644 --- a/vendor/github.com/rs/zerolog/context.go +++ b/vendor/github.com/rs/zerolog/context.go @@ -3,7 +3,7 @@ package zerolog import ( "context" "fmt" - "io/ioutil" + "io" "math" "net" "time" @@ -23,7 +23,7 @@ func (c Context) Logger() Logger { // Only map[string]interface{} and []interface{} are accepted. []interface{} must // alternate string keys and arbitrary values, and extraneous ones are ignored. func (c Context) Fields(fields interface{}) Context { - c.l.context = appendFields(c.l.context, fields) + c.l.context = appendFields(c.l.context, fields, c.l.stack) return c } @@ -57,7 +57,7 @@ func (c Context) Array(key string, arr LogArrayMarshaler) Context { // Object marshals an object that implement the LogObjectMarshaler interface. func (c Context) Object(key string, obj LogObjectMarshaler) Context { - e := newEvent(LevelWriterAdapter{ioutil.Discard}, 0) + e := newEvent(LevelWriterAdapter{io.Discard}, 0) e.Object(key, obj) c.l.context = enc.AppendObjectData(c.l.context, e.buf) putEvent(e) @@ -66,7 +66,7 @@ func (c Context) Object(key string, obj LogObjectMarshaler) Context { // EmbedObject marshals and Embeds an object that implement the LogObjectMarshaler interface. func (c Context) EmbedObject(obj LogObjectMarshaler) Context { - e := newEvent(LevelWriterAdapter{ioutil.Discard}, 0) + e := newEvent(LevelWriterAdapter{io.Discard}, 0) e.EmbedObject(obj) c.l.context = enc.AppendObjectData(c.l.context, e.buf) putEvent(e) @@ -163,6 +163,22 @@ func (c Context) Errs(key string, errs []error) Context { // Err adds the field "error" with serialized err to the logger context. func (c Context) Err(err error) Context { + if c.l.stack && ErrorStackMarshaler != nil { + switch m := ErrorStackMarshaler(err).(type) { + case nil: + case LogObjectMarshaler: + c = c.Object(ErrorStackFieldName, m) + case error: + if m != nil && !isNilValue(m) { + c = c.Str(ErrorStackFieldName, m.Error()) + } + case string: + c = c.Str(ErrorStackFieldName, m) + default: + c = c.Interface(ErrorStackFieldName, m) + } + } + return c.AnErr(ErrorFieldName, err) } @@ -375,10 +391,19 @@ func (c Context) Durs(key string, d []time.Duration) Context { // Interface adds the field key with obj marshaled using reflection. func (c Context) Interface(key string, i interface{}) Context { + if obj, ok := i.(LogObjectMarshaler); ok { + return c.Object(key, obj) + } c.l.context = enc.AppendInterface(enc.AppendKey(c.l.context, key), i) return c } +// Type adds the field key with val's type using reflection. +func (c Context) Type(key string, val interface{}) Context { + c.l.context = enc.AppendType(enc.AppendKey(c.l.context, key), val) + return c +} + // Any is a wrapper around Context.Interface. func (c Context) Any(key string, i interface{}) Context { return c.Interface(key, i) diff --git a/vendor/github.com/rs/zerolog/event.go b/vendor/github.com/rs/zerolog/event.go index 2a5d3b087..5c949f8a7 100644 --- a/vendor/github.com/rs/zerolog/event.go +++ b/vendor/github.com/rs/zerolog/event.go @@ -164,7 +164,7 @@ func (e *Event) Fields(fields interface{}) *Event { if e == nil { return e } - e.buf = appendFields(e.buf, fields) + e.buf = appendFields(e.buf, fields, e.stack) return e } diff --git a/vendor/github.com/rs/zerolog/example.jsonl b/vendor/github.com/rs/zerolog/example.jsonl new file mode 100644 index 000000000..d73193d7d --- /dev/null +++ b/vendor/github.com/rs/zerolog/example.jsonl @@ -0,0 +1,7 @@ +{"time":"5:41PM","level":"info","message":"Starting listener","listen":":8080","pid":37556} +{"time":"5:41PM","level":"debug","message":"Access","database":"myapp","host":"localhost:4962","pid":37556} +{"time":"5:41PM","level":"info","message":"Access","method":"GET","path":"/users","pid":37556,"resp_time":23} +{"time":"5:41PM","level":"info","message":"Access","method":"POST","path":"/posts","pid":37556,"resp_time":532} +{"time":"5:41PM","level":"warn","message":"Slow request","method":"POST","path":"/posts","pid":37556,"resp_time":532} +{"time":"5:41PM","level":"info","message":"Access","method":"GET","path":"/users","pid":37556,"resp_time":10} +{"time":"5:41PM","level":"error","message":"Database connection lost","database":"myapp","pid":37556,"error":"connection reset by peer"} diff --git a/vendor/github.com/rs/zerolog/fields.go b/vendor/github.com/rs/zerolog/fields.go index c1eb5ce79..23606ddd5 100644 --- a/vendor/github.com/rs/zerolog/fields.go +++ b/vendor/github.com/rs/zerolog/fields.go @@ -12,13 +12,13 @@ func isNilValue(i interface{}) bool { return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0 } -func appendFields(dst []byte, fields interface{}) []byte { +func appendFields(dst []byte, fields interface{}, stack bool) []byte { switch fields := fields.(type) { case []interface{}: if n := len(fields); n&0x1 == 1 { // odd number fields = fields[:n-1] } - dst = appendFieldList(dst, fields) + dst = appendFieldList(dst, fields, stack) case map[string]interface{}: keys := make([]string, 0, len(fields)) for key := range fields { @@ -28,13 +28,13 @@ func appendFields(dst []byte, fields interface{}) []byte { kv := make([]interface{}, 2) for _, key := range keys { kv[0], kv[1] = key, fields[key] - dst = appendFieldList(dst, kv) + dst = appendFieldList(dst, kv, stack) } } return dst } -func appendFieldList(dst []byte, kvList []interface{}) []byte { +func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte { for i, n := 0, len(kvList); i < n; i += 2 { key, val := kvList[i], kvList[i+1] if key, ok := key.(string); ok { @@ -74,6 +74,21 @@ func appendFieldList(dst []byte, kvList []interface{}) []byte { default: dst = enc.AppendInterface(dst, m) } + + if stack && ErrorStackMarshaler != nil { + dst = enc.AppendKey(dst, ErrorStackFieldName) + switch m := ErrorStackMarshaler(val).(type) { + case nil: + case error: + if m != nil && !isNilValue(m) { + dst = enc.AppendString(dst, m.Error()) + } + case string: + dst = enc.AppendString(dst, m) + default: + dst = enc.AppendInterface(dst, m) + } + } case []error: dst = enc.AppendArrayStart(dst) for i, err := range val { diff --git a/vendor/github.com/rs/zerolog/globals.go b/vendor/github.com/rs/zerolog/globals.go index e1067deb6..b38a7fcee 100644 --- a/vendor/github.com/rs/zerolog/globals.go +++ b/vendor/github.com/rs/zerolog/globals.go @@ -108,6 +108,34 @@ var ( // DefaultContextLogger is returned from Ctx() if there is no logger associated // with the context. DefaultContextLogger *Logger + + // LevelColors are used by ConsoleWriter's consoleDefaultFormatLevel to color + // log levels. + LevelColors = map[Level]int{ + TraceLevel: colorBlue, + DebugLevel: 0, + InfoLevel: colorGreen, + WarnLevel: colorYellow, + ErrorLevel: colorRed, + FatalLevel: colorRed, + PanicLevel: colorRed, + } + + // FormattedLevels are used by ConsoleWriter's consoleDefaultFormatLevel + // for a short level name. + FormattedLevels = map[Level]string{ + TraceLevel: "TRC", + DebugLevel: "DBG", + InfoLevel: "INF", + WarnLevel: "WRN", + ErrorLevel: "ERR", + FatalLevel: "FTL", + PanicLevel: "PNC", + } + + // TriggerLevelWriterBufferReuseLimit is a limit in bytes that a buffer is dropped + // from the TriggerLevelWriter buffer pool if the buffer grows above the limit. + TriggerLevelWriterBufferReuseLimit = 64 * 1024 ) var ( diff --git a/vendor/github.com/rs/zerolog/log.go b/vendor/github.com/rs/zerolog/log.go index 834c7e604..9fec7cc31 100644 --- a/vendor/github.com/rs/zerolog/log.go +++ b/vendor/github.com/rs/zerolog/log.go @@ -118,7 +118,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "strconv" "strings" @@ -246,7 +245,7 @@ type Logger struct { // you may consider using sync wrapper. func New(w io.Writer) Logger { if w == nil { - w = ioutil.Discard + w = io.Discard } lw, ok := w.(LevelWriter) if !ok { @@ -326,10 +325,13 @@ func (l Logger) Sample(s Sampler) Logger { } // Hook returns a logger with the h Hook. -func (l Logger) Hook(h Hook) Logger { - newHooks := make([]Hook, len(l.hooks), len(l.hooks)+1) +func (l Logger) Hook(hooks ...Hook) Logger { + if len(hooks) == 0 { + return l + } + newHooks := make([]Hook, len(l.hooks), len(l.hooks)+len(hooks)) copy(newHooks, l.hooks) - l.hooks = append(newHooks, h) + l.hooks = append(newHooks, hooks...) return l } @@ -385,7 +387,14 @@ func (l *Logger) Err(err error) *Event { // // You must call Msg on the returned event in order to send the event. func (l *Logger) Fatal() *Event { - return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) }) + return l.newEvent(FatalLevel, func(msg string) { + if closer, ok := l.w.(io.Closer); ok { + // Close the writer to flush any buffered message. Otherwise the message + // will be lost as os.Exit() terminates the program immediately. + closer.Close() + } + os.Exit(1) + }) } // Panic starts a new message with panic level. The panic() function @@ -450,6 +459,14 @@ func (l *Logger) Printf(format string, v ...interface{}) { } } +// Println sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Println. +func (l *Logger) Println(v ...interface{}) { + if e := l.Debug(); e.Enabled() { + e.CallerSkipFrame(1).Msg(fmt.Sprintln(v...)) + } +} + // Write implements the io.Writer interface. This is useful to set as a writer // for the standard library log. func (l Logger) Write(p []byte) (n int, err error) { @@ -488,6 +505,9 @@ func (l *Logger) newEvent(level Level, done func(string)) *Event { // should returns true if the log event should be logged. func (l *Logger) should(lvl Level) bool { + if l.w == nil { + return false + } if lvl < l.level || lvl < GlobalLevel() { return false } diff --git a/vendor/github.com/rs/zerolog/pretty.png b/vendor/github.com/rs/zerolog/pretty.png index 242033686debf04c5b1756c166d05d09767594e3..1449e45d142c74d023eccd28c7f9e03b0af2d2b9 100644 GIT binary patch literal 118839 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

0hOT$`donwI~(&Fqvi(oj|qzVR4* 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]V;ZJ?Jx7eS(*KURO{y5^Zh7CMY>g9 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

8fq~0=I!v4AnE;d}cbadUlY^=u7 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 diff --git a/vendor/github.com/rs/zerolog/syslog.go b/vendor/github.com/rs/zerolog/syslog.go index c40828307..a2b7285d8 100644 --- a/vendor/github.com/rs/zerolog/syslog.go +++ b/vendor/github.com/rs/zerolog/syslog.go @@ -78,3 +78,12 @@ func (sw syslogWriter) WriteLevel(level Level, p []byte) (n int, err error) { n = len(p) return } + +// Call the underlying writer's Close method if it is an io.Closer. Otherwise +// does nothing. +func (sw syslogWriter) Close() error { + if c, ok := sw.w.(io.Closer); ok { + return c.Close() + } + return nil +} diff --git a/vendor/github.com/rs/zerolog/writer.go b/vendor/github.com/rs/zerolog/writer.go index 9b9ef88e8..41b394d71 100644 --- a/vendor/github.com/rs/zerolog/writer.go +++ b/vendor/github.com/rs/zerolog/writer.go @@ -27,6 +27,15 @@ func (lw LevelWriterAdapter) WriteLevel(l Level, p []byte) (n int, err error) { return lw.Write(p) } +// Call the underlying writer's Close method if it is an io.Closer. Otherwise +// does nothing. +func (lw LevelWriterAdapter) Close() error { + if closer, ok := lw.Writer.(io.Closer); ok { + return closer.Close() + } + return nil +} + type syncWriter struct { mu sync.Mutex lw LevelWriter @@ -57,6 +66,15 @@ func (s *syncWriter) WriteLevel(l Level, p []byte) (n int, err error) { return s.lw.WriteLevel(l, p) } +func (s *syncWriter) Close() error { + s.mu.Lock() + defer s.mu.Unlock() + if closer, ok := s.lw.(io.Closer); ok { + return closer.Close() + } + return nil +} + type multiLevelWriter struct { writers []LevelWriter } @@ -89,6 +107,20 @@ func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) { return n, err } +// Calls close on all the underlying writers that are io.Closers. If any of the +// Close methods return an error, the remainder of the closers are not closed +// and the error is returned. +func (t multiLevelWriter) Close() error { + for _, w := range t.writers { + if closer, ok := w.(io.Closer); ok { + if err := closer.Close(); err != nil { + return err + } + } + } + return nil +} + // MultiLevelWriter creates a writer that duplicates its writes to all the // provided writers, similar to the Unix tee(1) command. If some writers // implement LevelWriter, their WriteLevel method will be used instead of Write. @@ -180,3 +212,135 @@ func (w *FilteredLevelWriter) WriteLevel(level Level, p []byte) (int, error) { } return len(p), nil } + +var triggerWriterPool = &sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(make([]byte, 0, 1024)) + }, +} + +// TriggerLevelWriter buffers log lines at the ConditionalLevel or below +// until a trigger level (or higher) line is emitted. Log lines with level +// higher than ConditionalLevel are always written out to the destination +// writer. If trigger never happens, buffered log lines are never written out. +// +// It can be used to configure "log level per request". +type TriggerLevelWriter struct { + // Destination writer. If LevelWriter is provided (usually), its WriteLevel is used + // instead of Write. + io.Writer + + // ConditionalLevel is the level (and below) at which lines are buffered until + // a trigger level (or higher) line is emitted. Usually this is set to DebugLevel. + ConditionalLevel Level + + // TriggerLevel is the lowest level that triggers the sending of the conditional + // level lines. Usually this is set to ErrorLevel. + TriggerLevel Level + + buf *bytes.Buffer + triggered bool + mu sync.Mutex +} + +func (w *TriggerLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) { + w.mu.Lock() + defer w.mu.Unlock() + + // At first trigger level or above log line, we flush the buffer and change the + // trigger state to triggered. + if !w.triggered && l >= w.TriggerLevel { + err := w.trigger() + if err != nil { + return 0, err + } + } + + // Unless triggered, we buffer everything at and below ConditionalLevel. + if !w.triggered && l <= w.ConditionalLevel { + if w.buf == nil { + w.buf = triggerWriterPool.Get().(*bytes.Buffer) + } + + // We prefix each log line with a byte with the level. + // Hopefully we will never have a level value which equals a newline + // (which could interfere with reconstruction of log lines in the trigger method). + w.buf.WriteByte(byte(l)) + w.buf.Write(p) + return len(p), nil + } + + // Anything above ConditionalLevel is always passed through. + // Once triggered, everything is passed through. + if lw, ok := w.Writer.(LevelWriter); ok { + return lw.WriteLevel(l, p) + } + return w.Write(p) +} + +// trigger expects lock to be held. +func (w *TriggerLevelWriter) trigger() error { + if w.triggered { + return nil + } + w.triggered = true + + if w.buf == nil { + return nil + } + + p := w.buf.Bytes() + for len(p) > 0 { + // We do not use bufio.Scanner here because we already have full buffer + // in the memory and we do not want extra copying from the buffer to + // scanner's token slice, nor we want to hit scanner's token size limit, + // and we also want to preserve newlines. + i := bytes.IndexByte(p, '\n') + line := p[0 : i+1] + p = p[i+1:] + // We prefixed each log line with a byte with the level. + level := Level(line[0]) + line = line[1:] + var err error + if lw, ok := w.Writer.(LevelWriter); ok { + _, err = lw.WriteLevel(level, line) + } else { + _, err = w.Write(line) + } + if err != nil { + return err + } + } + + return nil +} + +// Trigger forces flushing the buffer and change the trigger state to +// triggered, if the writer has not already been triggered before. +func (w *TriggerLevelWriter) Trigger() error { + w.mu.Lock() + defer w.mu.Unlock() + + return w.trigger() +} + +// Close closes the writer and returns the buffer to the pool. +func (w *TriggerLevelWriter) Close() error { + w.mu.Lock() + defer w.mu.Unlock() + + if w.buf == nil { + return nil + } + + // We return the buffer only if it has not grown above the limit. + // This prevents accumulation of large buffers in the pool just + // because occasionally a large buffer might be needed. + if w.buf.Cap() <= TriggerLevelWriterBufferReuseLimit { + w.buf.Reset() + triggerWriterPool.Put(w.buf) + } + w.buf = nil + + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4e423ce65..7850ae139 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1683,7 +1683,7 @@ github.com/rs/cors # github.com/rs/xid v1.5.0 ## explicit; go 1.12 github.com/rs/xid -# github.com/rs/zerolog v1.31.0 +# github.com/rs/zerolog v1.32.0 ## explicit; go 1.15 github.com/rs/zerolog github.com/rs/zerolog/internal/cbor From 0099cb62a86b7c280cf8b26a72450c24edbac4d1 Mon Sep 17 00:00:00 2001 From: mmattel Date: Tue, 6 Feb 2024 12:32:43 +0100 Subject: [PATCH 022/112] [docs-only] Add .adoc table version to env-var-deltas --- docs/env-var-deltas/4.0.5-5.0.0.adoc | 872 ++++++++++++++++++ .../make-adoc-table-from-md.txt | 46 + 2 files changed, 918 insertions(+) create mode 100644 docs/env-var-deltas/4.0.5-5.0.0.adoc create mode 100644 docs/env-var-deltas/make-adoc-table-from-md.txt diff --git a/docs/env-var-deltas/4.0.5-5.0.0.adoc b/docs/env-var-deltas/4.0.5-5.0.0.adoc new file mode 100644 index 000000000..80e0c3315 --- /dev/null +++ b/docs/env-var-deltas/4.0.5-5.0.0.adoc @@ -0,0 +1,872 @@ +# Added Variables between oCIS 4.0.5 and oCIS 5.0.0-rc + +// table created based on the .md file per 2024.02.06 +// the table should be recreated/updated based on the final .md file + +[width="100%",cols="~,~,~,~",options="header"] +|=== +| Service| Variable| Description| Default + +| Available in multiple services +| `OCIS_CACHE_DISABLE_PERSISTENCE` +| Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured.| `false` + +| xref:{s-path}/antivirus.adoc[antivirus] +| `OCIS_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| + +| xref:{s-path}/audit.adoc[audit] +| `OCIS_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| + +| xref:{s-path}/auth-service.adoc[auth-service] +| `OCIS_TRACING_ENABLED` + +`AUTH_SERVICE_TRACING_ENABLED` +| Activates tracing.| +| +| `OCIS_TRACING_TYPE` + +`AUTH_SERVICE_TRACING_TYPE` +| The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."| +| +| `OCIS_TRACING_ENDPOINT` + +`AUTH_SERVICE_TRACING_ENDPOINT` +| The endpoint of the tracing agent.| +| +| `OCIS_TRACING_COLLECTOR` + +`AUTH_SERVICE_TRACING_COLLECTOR` +| The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset.| +| +| `OCIS_LOG_LEVEL` + +`AUTH_SERVICE_LOG_LEVEL` +| The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'."| +| +| `OCIS_LOG_PRETTY` + +`AUTH_SERVICE_LOG_PRETTY` +| Activates pretty log output.| +| +| `OCIS_LOG_COLOR` + +`AUTH_SERVICE_LOG_COLOR` +| Activates colorized log output.| +| +| `OCIS_LOG_FILE` + +`AUTH_SERVICE_LOG_FILE` +| The path to the log file. Activates logging to this file if set.| +| +| `AUTH_SERVICE_DEBUG_ADDR` +| Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed.| +| +| `AUTH_SERVICE_DEBUG_TOKEN` +| Enables pprof, which can be used for profiling.| +| +| `AUTH_SERVICE_DEBUG_ZPAGES` +| Enables zpages, which can be used for collecting and viewing in-memory traces.| +| +| `AUTH_SERVICE_GRPC_ADDR` +| The bind address of the GRPC service.| +| +| `AUTH_SERVICE_GRPC_PROTOCOL` +| The transport protocol of the GRPC service.| +| +| `OCIS_SERVICE_ACCOUNT_ID` + +`AUTH_SERVICE_SERVICE_ACCOUNT_ID` +| The ID of the service account the service should use. See the 'auth-service' service description for more details.| +| +| `OCIS_SERVICE_ACCOUNT_SECRET` + +`AUTH_SERVICE_SERVICE_ACCOUNT_SECRET` +| The service account secret.| + +| +| `OCIS_JWT_SECRET` + +`AUTH_SERVICE_JWT_SECRET` +| The secret to mint and validate jwt tokens.| + +| xref:{s-path}/clientlog.adoc[clientlog] +| `OCIS_REVA_GATEWAY` +| CS3 gateway used to look up user metadata| +| +| `OCIS_EVENTS_ENDPOINT` + +`CLIENTLOG_EVENTS_ENDPOINT` +| The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture.| +| +| `OCIS_EVENTS_CLUSTER` + +`CLIENTLOG_EVENTS_CLUSTER` +| The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system.| +| +| `OCIS_INSECURE` + +`CLIENTLOG_EVENTS_TLS_INSECURE` +| Whether to verify the server TLS certificates.| +| +| `OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE` + +`CLIENTLOG_EVENTS_TLS_ROOT_CA_CERTIFICATE` +| The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false.| +| +| `OCIS_EVENTS_ENABLE_TLS` + +`CLIENTLOG_EVENTS_ENABLE_TLS` +| Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`CLIENTLOG_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`CLIENTLOG_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_JWT_SECRET` + +`CLIENTLOG_JWT_SECRET` +| The secret to mint and validate jwt tokens.| +| +| `OCIS_SERVICE_ACCOUNT_ID` + +`CLIENTLOG_SERVICE_ACCOUNT_ID` +| The ID of the service account the service should use. See the 'auth-service' service description for more details.| +| +| `OCIS_SERVICE_ACCOUNT_SECRET` + +`CLIENTLOG_SERVICE_ACCOUNT_SECRET` +| The service account secret.| + +| +| `CLIENTLOG_DEBUG_ADDR` +| Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed.| +| +| `CLIENTLOG_DEBUG_TOKEN` +| Token to secure the metrics endpoint.| +| +| `CLIENTLOG_DEBUG_PPROF` +| Enables pprof, which can be used for profiling.| +| +| `CLIENTLOG_DEBUG_ZPAGES` +| Enables zpages, which can be used for collecting and viewing in-memory traces.| + +| +| `OCIS_LOG_LEVEL` + +`CLIENTLOG_USERLOG_LOG_LEVEL` +| The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'."| +| +| `OCIS_LOG_PRETTY` + +`CLIENTLOG_USERLOG_LOG_PRETTY` +| Activates pretty log output.| +| +| `OCIS_LOG_COLOR` + +`CLIENTLOG_USERLOG_LOG_COLOR` +| Activates colorized log output.| +| +| `OCIS_LOG_FILE` + +`CLIENTLOG_USERLOG_LOG_FILE` +| The path to the log file. Activates logging to this file if set.| + +| +| `OCIS_TRACING_ENABLED` + +`CLIENTLOG_TRACING_ENABLED` +| Activates tracing.| +| +| `OCIS_TRACING_TYPE` + +`CLIENTLOG_TRACING_TYPE` +| The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` +| +| +| `OCIS_TRACING_ENDPOINT` + +`CLIENTLOG_TRACING_ENDPOINT` +| The endpoint of the tracing agent.| +| +| `OCIS_TRACING_COLLECTOR` + +`CLIENTLOG_TRACING_COLLECTOR` +| The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset.| + +| xref:{s-path}/eventhistory.adoc[eventhistory] +| `OCIS_PERSISTENT_STORE_AUTH_USERNAME` + +`EVENTHISTORY_STORE_AUTH_USERNAME` +| The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured.| +| +| `OCIS_PERSISTENT_STORE_AUTH_PASSWORD` + +`EVENTHISTORY_STORE_AUTH_PASSWORD` +| The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured.| +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`EVENTHISTORY_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`EVENTHISTORY_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| + +| xref:{s-path}/frontend.adoc[frontend] +| `OCIS_ENABLE_RESHARING` +| Changing this value is NOT supported. Enables the support for resharing in the clients.| +| +| `FRONTEND_DEFAULT_LINK_PERMISSIONS` +| Defines the default permissions a link is being created with. Possible values are 0 (= internal link, for instance members only) and 1 (= public link with viewer permissions). Defaults to 1.| +| +| `FRONTEND_AUTO_ACCEPT_SHARES` +| Defines if shares should be auto accepted by default. Users can change this setting individually in their profile.| +| +| `OCIS_CACHE_DISABLE_PERSISTENCE` + +`FRONTEND_OCS_STAT_CACHE_DISABLE_PERSISTENCE` +| Disable persistence of the cache. Only applies when using the 'nats-js-kv' store type. Defaults to false.| false +| +| `FRONTEND_OCS_LIST_OCM_SHARES` +| Include OCM shares when listing shares. See the OCM service documentation for more details.| +| +| `OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD` + +`FRONTEND_OCS_PUBLIC_SHARE_MUST_HAVE_PASSWORD` +| Set this to true if you want to enforce passwords on all public shares.| +| +| `FRONTEND_OCS_INCLUDE_OCM_SHAREES` +| nclude OCM sharees when listing sharees.| +| +| `OCIS_EVENTS_ENDPOINT` + +`FRONTEND_EVENTS_ENDPOINT` +| The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture.| +| +| `OCIS_EVENTS_CLUSTER` + +`FRONTEND_EVENTS_CLUSTER` +| The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system.| +| +| `OCIS_INSECURE` + +`FRONTEND_EVENTS_TLS_INSECURE` +| Whether to verify the server TLS certificates.| +| +| `FRONTEND_EVENTS_TLS_ROOT_CA_CERTIFICATE` + +`OCS_EVENTS_TLS_ROOT_CA_CERTIFICATE` +| The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false.| +| +| `OCIS_EVENTS_ENABLE_TLS` + +`FRONTEND_EVENTS_ENABLE_TLS` +| Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`FRONTEND_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`FRONTEND_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_SERVICE_ACCOUNT_ID` + +`FRONTEND_SERVICE_ACCOUNT_ID` +| The ID of the service account the service should use. See the 'auth-service' service description for more details.| +| +| `OCIS_SERVICE_ACCOUNT_SECRET` + +`FRONTEND_SERVICE_ACCOUNT_SECRET` +| The service account secret.| +| +| `OCIS_PASSWORD_POLICY_MIN_CHARACTERS` + +`FRONTEND_PASSWORD_POLICY_MIN_CHARACTERS` +| Define the minimum password length. Defaults to 0 if not set.| 0 +| +| `OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS` + +`FRONTEND_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS` +| Define the minimum number of lowercase characters. Defaults to 0 if not set.| 0 +| +| `OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS` + +`FRONTEND_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS` +| Define the minimum number of uppercase characters. Defaults to 0 if not set.| 0 +| +| `OCIS_PASSWORD_POLICY_MIN_DIGITS` + +`FRONTEND_PASSWORD_POLICY_MIN_DIGITS` +| Define the minimum number of digits. Defaults to 0 if not set.| 0 +| +| `OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS` + +`FRONTEND_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS` +| Define the minimum number of special characters. Defaults to 0 if not set.| 0 +| +| `OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST` + +`FRONTEND_PASSWORD_POLICY_BANNED_PASSWORDS_LIST` +| Path to the 'banned passwords list' file. See the documentation for more details.| + +| xref:{s-path}/gateway.adoc[gateway] +| `GATEWAY_STORAGE_REGISTRY_DRIVER` +| The driver name of the storage registry to use.| +| +| `GATEWAY_STORAGE_REGISTRY_RULES` +| The rules for the storage registry. See the Environment Variable Types description for more details.| +| +| `GATEWAY_STORAGE_REGISTRY_CONFIG_JSON` +| Additional configuration for the storage registry in json format.| +| +| `OCIS_CACHE_DISABLE_PERSISTENCE` + +`GATEWAY_STAT_CACHE_DISABLE_PERSISTENCE` +| Disables persistence of the stat cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false.| false +| +| `OCIS_CACHE_DISABLE_PERSISTENCE` + +`GATEWAY_PROVIDER_CACHE_DISABLE_PERSISTENCE` +| Disables persistence of the provider cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false.| false +| +| `OCIS_CACHE_DISABLE_PERSISTENCE` + +`GATEWAY_CREATE_HOME_CACHE_DISABLE_PERSISTENCE` +| Disables persistence of the create home cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false.| false + +| xref:{s-path}/graph.adoc[graph] +| `OCIS_CACHE_DISABLE_PERSISTENCE` + +`GRAPH_CACHE_DISABLE_PERSISTENCE` +| Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false.| false +| +| `OCIS_CACHE_AUTH_USERNAME` + +`GRAPH_CACHE_AUTH_USERNAME` +| The username to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured.| +| +| `OCIS_CACHE_AUTH_PASSWORD` + +`GRAPH_CACHE_AUTH_PASSWORD` +| The password to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured.| + +| +| `GRAPH_IDENTITY_SEARCH_MIN_LENGTH` +| The minimum length the search term needs to have for unprivileged users when searching for users or groups.| +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`GRAPH_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`GRAPH_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_SERVICE_ACCOUNT_ID` + +`GRAPH_SERVICE_ACCOUNT_ID` +| The ID of the service account the service should use. See the 'auth-service' service description for more details.| +| +| `OCIS_SERVICE_ACCOUNT_SECRET` + +`GRAPH_SERVICE_ACCOUNT_SECRET` +| The service account secret.| +| +| `OCIS_ENABLE_RESHARING` + +`GRAPH_ENABLE_RESHARING` +| Changing this value is NOT supported. Enables the support for resharing in the clients.| + +| xref:{s-path}/notifications.adoc[notifications] +| `OCIS_DEFAULT_LANGUAGE` +| The default language used by services and the WebUI. If not defined, English will be used as default. See the documentation for more details.| +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`NOTIFICATIONS_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`NOTIFICATIONS_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_SERVICE_ACCOUNT_ID` + +`NOTIFICATIONS_SERVICE_ACCOUNT_ID` +| The ID of the service account the service should use. See the 'auth-service' service description for more details.| +| +| `OCIS_SERVICE_ACCOUNT_SECRET` + +`NOTIFICATIONS_SERVICE_ACCOUNT_SECRET` +| The service account secret.| + +| xref:{s-path}/ocdav.adoc[ocdav] +| `OCDAV_OCM_NAMESPACE` +| The human readable path prefix for the ocm shares.| + +| xref:{s-path}/ocm.adoc[ocm] +| `SHARING_OCM_PROVIDER_AUTHORIZER_DRIVER` +| Driver to be used to persist ocm invites. Supported value is only 'json'.| `json` +| +| `OCM_HTTP_ADDR` +| The bind address of the HTTP service.| +| +| `OCM_HTTP_PROTOCOL` +| The transport protocol of the HTTP service.| +| +| `OCM_HTTP_PREFIX` +| The path prefix where OCM can be accessed (defaults to /).| +| +| `OCIS_SERVICE_ACCOUNT_ID` + +`OCM_SERVICE_ACCOUNT_ID` +| The ID of the service account the service should use. See the 'auth-service' service description for more details.| +| +| `OCIS_SERVICE_ACCOUNT_SECRET` + +`OCM_SERVICE_ACCOUNT_SECRET` +| The service account secret.| +| +| `OCIS_CORS_ALLOW_ORIGINS` + +`OCM_CORS_ALLOW_ORIGINS` +| A list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin. See the Environment Variable Types description for more details.| +| +| `OCIS_CORS_ALLOW_METHODS` + +`OCM_CORS_ALLOW_METHODS` +| A list of allowed CORS methods. See following chapter for more details: *Access-Control-Allow-Methods* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods. See the Environment Variable Types description for more details.| +| +| `OCIS_CORS_ALLOW_HEADERS` + +`OCM_CORS_ALLOW_HEADERS` +| A list of allowed CORS headers. See following chapter for more details: *Access-Control-Allow-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers. See the Environment Variable Types description for more details.| +| +| `OCIS_CORS_ALLOW_CREDENTIALS` + +`OCM_CORS_ALLOW_CREDENTIALS` +| Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials.| +| +| `OCM_GRPC_ADDR` +| The bind address of the GRPC service.| +| +| `OCM_GRPC_PROTOCOL` +| The transport protocol of the GRPC service.| +| +| `OCM_SCIENCEMESH_PREFIX` +| URL path prefix for the ScienceMesh service. Note that the string must not start with '/'.| +| +| `OCM_MESH_DIRECTORY_URL` +| URL of the mesh directory service.| +| +| `OCM_OCMD_PREFIX` +| URL path prefix for the OCMd service. Note that the string must not start with '/'.| +| +| `OCM_OCMD_EXPOSE_RECIPIENT_DISPLAY_NAME` +| Expose the display name of OCM share recipients.| +| +| `OCM_OCM_INVITE_MANAGER_DRIVER` +| Driver to be used to persist ocm invites. Supported value is only 'json'.| `json` +| +| `OCM_OCM_INVITE_MANAGER_INSECURE` +| Disable TLS certificate validation for the OCM connections. Do not set this in production environments.| +| +| `OCM_OCM_INVITE_MANAGER_JSON_FILE` +| Path to the JSON file where OCM invite data will be stored. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage.| +| +| `OCM_OCM_PROVIDER_AUTHORIZER_PROVIDERS_FILE` +| Path to the JSON file where ocm invite data will be stored. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage.| +| +| `OCM_OCM_PROVIDER_AUTHORIZER_VERIFY_REQUEST_HOSTNAME` +| Verify the hostname of the request against the hostname of the OCM provider.| +| +| `OCM_OCM_CORE_DRIVER` +| Driver to be used to persist ocm shares. Supported value is only 'json'.| `json` +| +| `OCM_OCM_STORAGE_PROVIDER_INSECURE` +| Disable TLS certificate validation for the OCM connections. Do not set this in production environments.| +| +| `OCM_OCM_STORAGE_PROVIDER_STORAGE_ROOT` +| Directory where the ocm storage provider persists its data like tus upload info files.| +| +| `OCM_OCM_CORE_JSON_FILE` +| Path to the JSON file where OCM share data will be stored. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage.| +| +| `OCM_OCM_SHARE_PROVIDER_DRIVER` +| Driver to be used to persist ocm shares. Supported value is only 'json'.| `json` +| +| `OCM_OCM_SHARE_PROVIDER_INSECURE` +| Disable TLS certificate validation for the OCM connections. Do not set this in production environments.| +| +| `OCM_WEBAPP_TEMPLATE` +| 'Template for the webapp url.| +| +| `OCM_OCM_SHAREPROVIDER_JSON_FILE` +| Path to the JSON file where OCM share data will be stored. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage.| + +| +| `OCM_DEBUG_ADDR` +| Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed.| +| +| `OCM_DEBUG_TOKEN` +| Token to secure the metrics endpoint.| +| +| `OCM_DEBUG_PPROF` +| Enables pprof, which can be used for profiling.| +| +| `OCM_DEBUG_ZPAGES` +| Enables zpages, which can be used for collecting and viewing in-memory traces.| + +| +| `OCIS_LOG_LEVEL` + +`OCM_LOG_LEVEL` +| The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'."| +| +| `OCIS_LOG_PRETTY` + +`OCM_LOG_PRETTY` +| Activates pretty log output.| +| +| `OCIS_LOG_COLOR` + +`OCM_LOG_COLOR` +| Activates colorized log output.| +| +| `OCIS_LOG_FILE` + +`OCM_LOG_FILE` +| The path to the log file. Activates logging to this file if set.| + +| +| `OCIS_TRACING_ENABLED` + +`OCM_TRACING_ENABLED` +| Activates tracing.| +| +| `OCIS_TRACING_TYPE` + +`OCM_TRACING_TYPE` +| The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` +| +| +| `OCIS_TRACING_ENDPOINT` + +`OCM_TRACING_ENDPOINT` +| The endpoint of the tracing agent.| +| +| `OCIS_TRACING_COLLECTOR` + +`OCM_TRACING_COLLECTOR` +| The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset.| + +| xref:{s-path}/policies.adoc[policies] +| `OCIS_EVENTS_AUTH_USERNAME` + +`POLICIES_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`POLICIES_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| + +| +| `OCIS_TRACING_ENABLED` + +`POLICIES_TRACING_ENABLED` +| Activates tracing.| +| +| `OCIS_TRACING_TYPE` + +`POLICIES_TRACING_TYPE` +| The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` +| +| +| `OCIS_TRACING_ENDPOINT` + +`POLICIES_TRACING_ENDPOINT` +| The endpoint of the tracing agent.| +| +| `OCIS_TRACING_COLLECTOR` + +`POLICIES_TRACING_COLLECTOR` +| The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset.| + +| xref:{s-path}/postprocessing.adoc[postprocessing] +| `POSTPROCESSING_RETRY_BACKOFF_DURATION` +| The base for the exponential backoff duration before retrying a failed postprocessing step. See the Environment Variable Types description for more details.| +| +| `POSTPROCESSING_MAX_RETRIES` +| The maximum number of retries for a failed postprocessing step.| +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`POSTPROCESSING_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`POSTPROCESSING_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_PERSISTENT_STORE_AUTH_USERNAME` + +`POSTPROCESSING_STORE_AUTH_USERNAME` +| The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured.| +| +| `OCIS_PERSISTENT_STORE_AUTH_PASSWORD` + +`POSTPROCESSING_STORE_AUTH_PASSWORD` +| The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured.| + +| +| `OCIS_TRACING_ENABLED` + +`POSTPROCESSING_TRACING_ENABLED` +| Activates tracing.| +| +| `OCIS_TRACING_TYPE` + +`POSTPROCESSING_TRACING_TYPE` +| The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` +| +| +| `OCIS_TRACING_ENDPOINT` + +`POSTPROCESSING_TRACING_ENDPOINT` +| The endpoint of the tracing agent.| +| +| `OCIS_TRACING_COLLECTOR` + +`POSTPROCESSING_TRACING_COLLECTOR` +| The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset.| + +| xref:{s-path}/proxy.adoc[proxy] +| `OCIS_CACHE_AUTH_USERNAME` + +`PROXY_OIDC_USERINFO_CACHE_AUTH_USERNAME` +| The username to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured.| +| +| `OCIS_CACHE_AUTH_PASSWORD` + +`PROXY_OIDC_USERINFO_CACHE_AUTH_PASSWORD` +| The password to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured.| +| +| `OCIS_SERVICE_ACCOUNT_ID` + +`PROXY_SERVICE_ACCOUNT_ID` +| The ID of the service account the service should use. See the 'auth-service' service description for more details.| +| +| `OCIS_SERVICE_ACCOUNT_SECRET` + +`PROXY_SERVICE_ACCOUNT_SECRET` +| The service account secret.| + +| xref:{s-path}/search.adoc[search] +| `OCIS_SERVICE_ACCOUNT_ID` + +`SEARCH_SERVICE_ACCOUNT_ID` +| The ID of the service account the service should use. See the 'auth-service' service description for more details.| +| +| `OCIS_SERVICE_ACCOUNT_SECRET` + +`SEARCH_SERVICE_ACCOUNT_SECRET` +| The service account secret.| + +| +| `SEARCH_EXTRACTOR_TIKA_CLEAN_STOP_WORDS` +| Defines if stop words should be cleaned or not. See the documentation for more details.| + +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`SEARCH_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`SEARCH_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| + +| xref:{s-path}/settings.adoc[settings] +| `SETTINGS_SERVICE_ACCOUNT_IDS` + +`OCIS_SERVICE_ACCOUNT_ID` +| The list of all service account IDs. These will be assigned the hidden 'service-account' role. Note: When using 'OCIS_SERVICE_ACCOUNT_ID' this will contain only one value while 'SETTINGS_SERVICE_ACCOUNT_IDS' can have multiple. See the 'auth-service' service description for more details about service accounts.| +| +| `OCIS_DEFAULT_LANGUAGE` +| The default language used by services and the WebUI. If not defined, English will be used as default. See the documentation for more details.| +| +| `OCIS_CACHE_DISABLE_PERSISTENCE` + +`SETTINGS_CACHE_DISABLE_PERSISTENCE` +| Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false.| +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`SETTINGS_EVENTS_AUTH_USERNAME` +| The username to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`SETTINGS_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured.| + +| xref:{s-path}/sharing.adoc[sharing] +| `OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD` + +`SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD` +| Set this to true if you want to enforce passwords on all public shares.| +| +| `OCIS_PASSWORD_POLICY_MIN_CHARACTERS` + +`SHARING_PASSWORD_POLICY_MIN_CHARACTERS` +| Define the minimum password length. Defaults to 0 if not set.| 0 +| +| `OCIS_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS` + +`SHARING_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS` +| Define the minimum number of lowercase characters. Defaults to 0 if not set.| 0 +| +| `OCIS_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS` + +`SHARING_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS` +| Define the minimum number of uppercase characters. Defaults to 0 if not set.| 0 +| +| `OCIS_PASSWORD_POLICY_MIN_DIGITS` + +`SHARING_PASSWORD_POLICY_MIN_DIGITS` +| Define the minimum number of digits. Defaults to 0 if not set.| 0 +| +| `OCIS_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS` + +`SHARING_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS` +| Define the minimum number of special characters. Defaults to 0 if not set.| 0 +| +| `OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST` + +`SHARING_PASSWORD_POLICY_BANNED_PASSWORDS_LIST` +| Path to the 'banned passwords list' file. See the documentation for more details.| + +| xref:{s-path}/sse.adoc[sse] +| `OCIS_LOG_LEVEL` + +`SSE_LOG_LEVEL` +| The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'."| +| +| `OCIS_LOG_PRETTY` + +`SSE_LOG_PRETTY` +| Activates pretty log output.| +| +| `OCIS_LOG_COLOR` + +`SSE_LOG_COLOR` +| Activates colorized log output.| +| +| `OCIS_LOG_FILE` + +`SSE_LOG_FILE` +| The path to the log file. Activates logging to this file if set.| +| +| `SSE_DEBUG_ADDR` +| Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed.| +| +| `SSE_DEBUG_TOKEN` +| Token to secure the metrics endpoint.| +| +| `SSE_DEBUG_PPROF` +| Enables pprof, which can be used for profiling.| +| +| `SSE_DEBUG_ZPAGES` +| Enables zpages, which can be used for collecting and viewing in-memory traces.| +| +| `OCIS_EVENTS_ENDPOINT` + +`SSE_EVENTS_ENDPOINT` +| The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture.| +| +| `OCIS_EVENTS_CLUSTER` + +`SSE_EVENTS_CLUSTER` +| The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system.| +| +| `OCIS_INSECURE` + +`SSE_EVENTS_TLS_INSECURE` +| Whether to verify the server TLS certificates.| +| +| `OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE` + +`SSE_EVENTS_TLS_ROOT_CA_CERTIFICATE` +| The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false.| +| +| `OCIS_EVENTS_ENABLE_TLS` + +`SSE_EVENTS_ENABLE_TLS` +| Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`SSE_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`SSE_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_CORS_ALLOW_ORIGINS` + +`SSE_CORS_ALLOW_ORIGINS` +| A list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin. See the Environment Variable Types description for more details.| +| +| `OCIS_CORS_ALLOW_METHODS` + +`SSE_CORS_ALLOW_METHODS` +| A list of allowed CORS methods. See following chapter for more details: *Access-Control-Allow-Methods* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods. See the Environment Variable Types description for more details.| +| +| `OCIS_CORS_ALLOW_HEADERS` + +`SSE_CORS_ALLOW_HEADERS` +| A list of allowed CORS headers. See following chapter for more details: *Access-Control-Allow-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers. See the Environment Variable Types description for more details.| +| +| `OCIS_CORS_ALLOW_CREDENTIALS` + +`SSE_CORS_ALLOW_CREDENTIALS` +| Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials.| +| +| `SSE_HTTP_ADDR` +| The bind address of the HTTP service. | +| +| `SSE_HTTP_ROOT` +| The root path of the HTTP service.| +| +| `OCIS_JWT_SECRET` + +`SSE_JWT_SECRET` +| The secret to mint and validate jwt tokens.| + +| +| `OCIS_TRACING_ENABLED` + +`SSE_TRACING_ENABLED` +| Activates tracing.| +| +| `OCIS_TRACING_TYPE` + +`SSE_TRACING_TYPE` +| The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` +| +| +| `OCIS_TRACING_ENDPOINT` + +`SSE_TRACING_ENDPOINT` +| The endpoint of the tracing agent.| +| +| `OCIS_TRACING_COLLECTOR` + +`SSE_TRACING_COLLECTOR` +| The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset.| + +| xref:{s-path}/storage-system.adoc[storage-system] +| `OCIS_CACHE_DISABLE_PERSISTENCE` + +`STORAGE_SYSTEM_CACHE_DISABLE_PERSISTENCE` +| Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false.| false + +| xref:{s-path}/storage-users.adoc[storage-users] +| `OCIS_GATEWAY_GRPC_ADDR` + +`STORAGE_USERS_GATEWAY_GRPC_ADDR` +| The bind address of the gateway GRPC address.| +| +| `OCIS_MACHINE_AUTH_API_KEY` +| Machine auth API key used to validate internal requests necessary for the access to resources from other services.| +| +| `STORAGE_USERS_CLI_MAX_ATTEMPTS_RENAME_FILE` +| The maximum number of attempts to rename a file when a user restores a file to an existing destination with the same name. The minimum value is 100.| +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`STORAGE_USERS_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`STORAGE_USERS_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_CACHE_DISABLE_PERSISTENCE` + +`STORAGE_USERS_STAT_CACHE_DISABLE_PERSISTENCE` +| Disables persistence of the stat cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false.| false +| +| `OCIS_CACHE_DISABLE_PERSISTENCE` + +`STORAGE_USERS_FILEMETADATA_CACHE_DISABLE_PERSISTENCE` +| Disables persistence of the file metadata cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false.| false +| +| `OCIS_CACHE_DISABLE_PERSISTENCE` + +`STORAGE_USERS_ID_CACHE_DISABLE_PERSISTENCE` +| Disables persistence of the id cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false.| false +| +| `OCIS_SERVICE_ACCOUNT_ID` + +`STORAGE_USERS_SERVICE_ACCOUNT_ID` +| The ID of the service account the service should use. See the 'auth-service' service description for more details.| +| +| `OCIS_SERVICE_ACCOUNT_SECRET` + +`STORAGE_USERS_SERVICE_ACCOUNT_SECRET` +| The service account secret.| + +| xref:{s-path}/userlog.adoc[userlog] +| `OCIS_DEFAULT_LANGUAGE` +| The default language used by services and the WebUI. If not defined, English will be used as default. See the documentation for more details.| +| +| `OCIS_PERSISTENT_STORE_AUTH_USERNAME` + +`USERLOG_STORE_AUTH_USERNAME` +| The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured.| +| +| `OCIS_PERSISTENT_STORE_AUTH_PASSWORD` + +`USERLOG_STORE_AUTH_PASSWORD` +| The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured.| +| +| `OCIS_EVENTS_AUTH_USERNAME` + +`USERLOG_EVENTS_AUTH_USERNAME` +| The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_EVENTS_AUTH_PASSWORD` + +`USERLOG_EVENTS_AUTH_PASSWORD` +| The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `OCIS_SERVICE_ACCOUNT_ID` + +`USERLOG_SERVICE_ACCOUNT_ID` +| The ID of the service account the service should use. See the 'auth-service' service description for more details.| +| +| `OCIS_SERVICE_ACCOUNT_SECRET` + +`USERLOG_SERVICE_ACCOUNT_SECRET` +| The service account secret.| + +| xref:{s-path}/web.adoc[web] +| `WEB_OPTION_LOGIN_URL` +| Specifies the target URL to the login page. This is helpful when an external IdP is used. This option is disabled by default. Example URL like: https://www.myidp.com/login.| +| +| `WEB_OPTION_DISABLED_EXTENSIONS` +| A list to disable specific Web extensions identified by their ID. The ID can e.g. be taken from the 'index.ts' file of the web extension. Example: 'com.github.owncloud.web.files.search,com.github.owncloud.web.files.print'. See the Environment Variable Types description for more details.| +| +| `WEB_OPTION_USER_LIST_REQUIRES_FILTER` +| Defines whether one or more filters must be set in order to list users in the Web admin settings. Set this option to 'true' if running in an environment with a lot of users and listing all users could slow down performance. Defaults to 'false'.| false +| +| `WEB_OPTION_EMBED_ENABLED` +| Defines whether Web should be running in 'embed' mode. Setting this to 'true' will enable a stripped down version of Web with reduced functionality used to integrate Web into other applications like via iFrame. Setting it to 'false' or not setting it (default) will run Web as usual with all functionality enabled. See the text description for more details.| +| +| `WEB_OPTION_EMBED_TARGET` +| Defines how Web is being integrated when running in 'embed' mode. Currently, the only supported options are '' (empty) and 'location'. With '' which is the default, Web will run regular as defined via the 'embed.enabled' config option. With 'location', Web will run embedded as location picker. Resource selection will be disabled and the selected resources array always includes the current folder as the only item. See the text description for more details. | +| +| `WEB_OPTION_EMBED_MESSAGES_ORIGIN` +| Defines a URL under which Web can be integrated via iFrame in 'embed' mode. Note that setting this is mandatory when running Web in 'embed' mode. Use '*' as value to allow running the iFrame under any URL, although this is not recommended for security reasons. See the text description for more details.| +| +| `WEB_OPTION_EMBED_DELEGATE_AUTHENTICATION` +| Defines whether Web should require authentication to be done by the parent application when running in 'embed' mode. If set to 'true' Web will not try to authenticate the user on its own but will require an access token coming from the parent application. Defaults to being unset.| +| +| `WEB_OPTION_EMBED_DELEGATE_AUTHENTICATION_ORIGIN` +| Defines the host to validate the message event origin against when running Web in 'embed' mode with delegated authentication. Defaults to event message origin validation being omitted, which is only recommended for development setups.| +| +| `WEB_OPTION_CONCURRENT_REQUESTS_RESOURCE_BATCH_ACTIONS` +| Defines the maximum number of concurrent requests per file/folder/space batch action. Defaults to 4.| +| +| `WEB_OPTION_CONCURRENT_REQUESTS_SSE` +| Defines the maximum number of concurrent requests in SSE event handlers. Defaults to 4.| +| +| `WEB_OPTION_CONCURRENT_REQUESTS_SHARES_CREATE` +| Defines the maximum number of concurrent requests per sharing invite batch. Defaults to 4.| +| +| `WEB_OPTION_CONCURRENT_REQUESTS_SHARES_LIST` +| Defines the maximum number of concurrent requests when loading individual share information inside listings. Defaults to 2.| +|=== diff --git a/docs/env-var-deltas/make-adoc-table-from-md.txt b/docs/env-var-deltas/make-adoc-table-from-md.txt new file mode 100644 index 000000000..126ecac48 --- /dev/null +++ b/docs/env-var-deltas/make-adoc-table-from-md.txt @@ -0,0 +1,46 @@ +make the markdown a asciidoc table + +cp 4.0.5-5.0.0.md 4.0.5-5.0.0.adoc copy file +sed -i 's/.$//g' 4.0.5-5.0.0.adoc remove last character of each line which is | +sed -i 's/ \+//g' 4.0.5-5.0.0.adoc remove any double spaces +fully remove the line containing |----...| +rename: | File| Variable| Description| Default --> | Service| Variable| Description| Default + +add on top under the page heading: +[width="100%",cols="~,~,~,~",options="header"] +|=== + +and at the bottom +|=== + +check SSE_HTTP_ADDR and SSE_HTTP_ROOT, there is one | too much +check WEB_OPTION_LOGIN_URL, there is one | missing +check WEB_OPTION_DISABLED_EXTENSIONS, in the linen before, there is one | too much + + +sed -i "s/\;/\` \+\n\`/g" 4.0.5-5.0.0.adoc make each envvar a single line for ease of readbility +sed -i "s/\`|/\`\n|/g" 4.0.5-5.0.0.adoc put the next | after the last envvar in a line into a new line +sed -i "s/|| \`/|\n|\`/g" 4.0.5-5.0.0.adoc split the || before an envvar into two lines +sed -i "s/services\/(.*)\//xref\:\{s-path\}\/\1\.adoc/g" 4.0.5-5.0.0.adoc make "service" a xref + +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/config.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/reva.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/debug.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/log.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/tracing.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/cache.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/content.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/search.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/options.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc + services/antivirus/pkg/config/config.go --> xref:{s-path}/antivirus.adoc[antivirus] + +change: ocis-pkg/shared/shared_types.go --> Available in multiple services + +sed -i "s/\]|/\]\n|/g" 4.0.5-5.0.0.adoc put tab separator of service and envvar into a new line +sed -i "s/|\`/| \`/g" 4.0.5-5.0.0.adoc |`OCIS --> | `OCIS + +sed -i 's/\.\./\./g' 4.0.5-5.0.0.adoc remove trailing double dots in sentences + +manually fix (remove) any double occurrences if a service like clientlog, auth-service, graph, ocm, policies, postprocessing, search, sse. this is because the origin path points to different go routines inside the same service. + +manually checking the result and fixing issues (mostly minor) is mandatory From 0c45b155be0fc320f9c2e892e504040f2a9a8e0a Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 6 Feb 2024 12:57:41 +0100 Subject: [PATCH 023/112] Update docs/env-var-deltas/4.0.5-5.0.0.adoc --- docs/env-var-deltas/4.0.5-5.0.0.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/env-var-deltas/4.0.5-5.0.0.adoc b/docs/env-var-deltas/4.0.5-5.0.0.adoc index 80e0c3315..d4a4a066b 100644 --- a/docs/env-var-deltas/4.0.5-5.0.0.adoc +++ b/docs/env-var-deltas/4.0.5-5.0.0.adoc @@ -1,4 +1,5 @@ -# Added Variables between oCIS 4.0.5 and oCIS 5.0.0-rc +// # Added Variables between oCIS 4.0.5 and oCIS 5.0.0-rc +// commenting the headline to make it better includable // table created based on the .md file per 2024.02.06 // the table should be recreated/updated based on the final .md file From dfbb5c8a6a56fa7cb75e57540082ebcaecf3ee10 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 6 Feb 2024 12:58:15 +0100 Subject: [PATCH 024/112] Update docs/env-var-deltas/make-adoc-table-from-md.txt Co-authored-by: Phil Davis --- docs/env-var-deltas/make-adoc-table-from-md.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/env-var-deltas/make-adoc-table-from-md.txt b/docs/env-var-deltas/make-adoc-table-from-md.txt index 126ecac48..9a5231200 100644 --- a/docs/env-var-deltas/make-adoc-table-from-md.txt +++ b/docs/env-var-deltas/make-adoc-table-from-md.txt @@ -18,7 +18,7 @@ check WEB_OPTION_LOGIN_URL, there is one | missing check WEB_OPTION_DISABLED_EXTENSIONS, in the linen before, there is one | too much -sed -i "s/\;/\` \+\n\`/g" 4.0.5-5.0.0.adoc make each envvar a single line for ease of readbility +sed -i "s/\;/\` \+\n\`/g" 4.0.5-5.0.0.adoc make each envvar a single line for ease of readability sed -i "s/\`|/\`\n|/g" 4.0.5-5.0.0.adoc put the next | after the last envvar in a line into a new line sed -i "s/|| \`/|\n|\`/g" 4.0.5-5.0.0.adoc split the || before an envvar into two lines sed -i "s/services\/(.*)\//xref\:\{s-path\}\/\1\.adoc/g" 4.0.5-5.0.0.adoc make "service" a xref From 54fbed0866c22225945663ff5c187b3ad028118d Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 6 Feb 2024 12:58:28 +0100 Subject: [PATCH 025/112] Update docs/env-var-deltas/make-adoc-table-from-md.txt Co-authored-by: Phil Davis --- docs/env-var-deltas/make-adoc-table-from-md.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/env-var-deltas/make-adoc-table-from-md.txt b/docs/env-var-deltas/make-adoc-table-from-md.txt index 9a5231200..47ffef568 100644 --- a/docs/env-var-deltas/make-adoc-table-from-md.txt +++ b/docs/env-var-deltas/make-adoc-table-from-md.txt @@ -15,7 +15,7 @@ and at the bottom check SSE_HTTP_ADDR and SSE_HTTP_ROOT, there is one | too much check WEB_OPTION_LOGIN_URL, there is one | missing -check WEB_OPTION_DISABLED_EXTENSIONS, in the linen before, there is one | too much +check WEB_OPTION_DISABLED_EXTENSIONS, in the line before, there is one | too much sed -i "s/\;/\` \+\n\`/g" 4.0.5-5.0.0.adoc make each envvar a single line for ease of readability From a72d7f57f3c7373c8805c18536bb98742d624661 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 6 Feb 2024 15:45:02 +0100 Subject: [PATCH 026/112] bump reva Signed-off-by: Christian Richter --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7ba7837ae..59f83d8b8 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/blevesearch/bleve/v2 v2.3.10 github.com/coreos/go-oidc/v3 v3.9.0 github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 - github.com/cs3org/reva/v2 v2.18.1-0.20240205065033-2c21ada2ae52 + github.com/cs3org/reva/v2 v2.18.1-0.20240206122233-bf89f7aaedd1 github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 github.com/disintegration/imaging v1.6.2 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e diff --git a/go.sum b/go.sum index 80a34ec6a..70eddac68 100644 --- a/go.sum +++ b/go.sum @@ -1019,8 +1019,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= -github.com/cs3org/reva/v2 v2.18.1-0.20240205065033-2c21ada2ae52 h1:Jeh8q6WKl4gcK7GMayn56y1uxbw91XvyuZcvY7SiDRk= -github.com/cs3org/reva/v2 v2.18.1-0.20240205065033-2c21ada2ae52/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= +github.com/cs3org/reva/v2 v2.18.1-0.20240206122233-bf89f7aaedd1 h1:74SlyiFIUYcTvSFaEyeyABg3nuiDko91ve0fE++t87s= +github.com/cs3org/reva/v2 v2.18.1-0.20240206122233-bf89f7aaedd1/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= From b802a1848ff1816dc062f9880f978fdef1e77bee Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 6 Feb 2024 15:45:13 +0100 Subject: [PATCH 027/112] update testsuite Signed-off-by: Christian Richter --- .../features/coreApiVersions/fileVersions.feature | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/acceptance/features/coreApiVersions/fileVersions.feature b/tests/acceptance/features/coreApiVersions/fileVersions.feature index 9658d6f61..56e1180aa 100644 --- a/tests/acceptance/features/coreApiVersions/fileVersions.feature +++ b/tests/acceptance/features/coreApiVersions/fileVersions.feature @@ -163,13 +163,13 @@ Feature: dav-versions Then the HTTP status code should be "200" And the following headers should be set | header | value | - | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" | + | Content-Disposition | attachment; filename*=UTF-8''"textfile0.txt"; filename="textfile0.txt" | And the downloaded content should be "version 1" When user "Alice" downloads the version of file "textfile0.txt" with the index "2" Then the HTTP status code should be "200" And the following headers should be set | header | value | - | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" | + | Content-Disposition | attachment; filename*=UTF-8''"textfile0.txt"; filename="textfile0.txt" | And the downloaded content should be "uploaded content" @skipOnStorage:ceph @skipOnStorage:scality @@ -182,13 +182,13 @@ Feature: dav-versions Then the HTTP status code should be "200" And the following headers should be set | header | value | - | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" | + | Content-Disposition | attachment; filename*=UTF-8''"textfile0.txt"; filename="textfile0.txt" | And the downloaded content should be "version 2" When user "Alice" downloads the version of file "textfile0.txt" with the index "2" Then the HTTP status code should be "200" And the following headers should be set | header | value | - | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" | + | Content-Disposition | attachment; filename*=UTF-8''"textfile0.txt"; filename="textfile0.txt" | And the downloaded content should be "uploaded content" From 41b33d53f24204fa111f4ce5c5635f53de01c57c Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 6 Feb 2024 15:48:22 +0100 Subject: [PATCH 028/112] vendor reva Signed-off-by: Christian Richter --- .../v2/internal/http/services/owncloud/ocdav/net/builders.go | 2 +- .../internal/http/services/owncloud/ocdav/propfind/propfind.go | 1 + vendor/modules.txt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net/builders.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net/builders.go index 0498072b6..be7b8fdee 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net/builders.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net/builders.go @@ -27,7 +27,7 @@ import ( // ContentDispositionAttachment builds a ContentDisposition Attachment header with various filename encodings func ContentDispositionAttachment(filename string) string { - return "attachment; filename*=UTF-8''" + filename + "; filename=\"" + filename + "\"" + return "attachment; filename*=UTF-8''\"" + filename + "\"; filename=\"" + filename + "\"" } // RFC1123Z formats a CS3 Timestamp to be used in HTTP headers like Last-Modified diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go index fc5764738..027f8c73b 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go @@ -579,6 +579,7 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r // adjust path info.Path = filepath.Join(spacePath, spaceRef.Path) + info.Name = filepath.Base(info.Path) spaceMap[info] = spaceData{Ref: spaceRef, SpaceType: space.SpaceType} diff --git a/vendor/modules.txt b/vendor/modules.txt index 7850ae139..9ebb835c7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -362,7 +362,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1 github.com/cs3org/go-cs3apis/cs3/types/v1beta1 -# github.com/cs3org/reva/v2 v2.18.1-0.20240205065033-2c21ada2ae52 +# github.com/cs3org/reva/v2 v2.18.1-0.20240206122233-bf89f7aaedd1 ## explicit; go 1.21 github.com/cs3org/reva/v2/cmd/revad/internal/grace github.com/cs3org/reva/v2/cmd/revad/runtime From afd4d54869bfaa08dadf47fc875273034e2215b0 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 6 Feb 2024 15:51:23 +0100 Subject: [PATCH 029/112] add changelog Signed-off-by: Christian Richter --- .../unreleased/bump-reva-for-content-disposition-header.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog/unreleased/bump-reva-for-content-disposition-header.md diff --git a/changelog/unreleased/bump-reva-for-content-disposition-header.md b/changelog/unreleased/bump-reva-for-content-disposition-header.md new file mode 100644 index 000000000..5c2cdbd7e --- /dev/null +++ b/changelog/unreleased/bump-reva-for-content-disposition-header.md @@ -0,0 +1,7 @@ +Bugfix: Fix Content-Deposition header for downloads + +We have fixed a bug that caused downloads to fail on Chromebased browsers when the filename contained special characters. + +https://github.com/owncloud/ocis/pull/8381 +https://github.com/owncloud/ocis/issues/8361 +https://github.com/cs3org/reva/pull/4498 From ee533d55cdff7ab976fb895b35b395cffca59035 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Wed, 7 Feb 2024 09:23:50 +0545 Subject: [PATCH 030/112] Update downloadFile feature for expected Content-Disposition --- .../features/coreApiWebdavOperations/downloadFile.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature b/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature index 34b7be65f..7c5eb31bc 100644 --- a/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature +++ b/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature @@ -269,7 +269,7 @@ Feature: download file Then the HTTP status code should be "200" And the following headers should be set | header | value | - | Content-Disposition | attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt" | + | Content-Disposition | attachment; filename*=UTF-8''"welcome.txt"; filename="welcome.txt" | | Content-Security-Policy | default-src 'none'; | | X-Content-Type-Options | nosniff | | X-Download-Options | noopen | From 1a0568ce5f2d020dcaecd0c906bd23509fc74264 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Wed, 7 Feb 2024 10:09:26 +0545 Subject: [PATCH 031/112] Fix typo in changelog --- .../unreleased/bump-reva-for-content-disposition-header.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/unreleased/bump-reva-for-content-disposition-header.md b/changelog/unreleased/bump-reva-for-content-disposition-header.md index 5c2cdbd7e..c87050ef2 100644 --- a/changelog/unreleased/bump-reva-for-content-disposition-header.md +++ b/changelog/unreleased/bump-reva-for-content-disposition-header.md @@ -1,4 +1,4 @@ -Bugfix: Fix Content-Deposition header for downloads +Bugfix: Fix Content-Disposition header for downloads We have fixed a bug that caused downloads to fail on Chromebased browsers when the filename contained special characters. From 3c8db1ae72062c4ccb1700cd4efe049f9adb1e80 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Wed, 7 Feb 2024 05:14:59 +0000 Subject: [PATCH 032/112] Automated changelog update [skip ci] --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b7f5b0e3..ca97c12c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ The following sections list the changes for unreleased. * Bugfix - Fix nats registry: [#8281](https://github.com/owncloud/ocis/pull/8281) * Bugfix - Remove invalid environment variables: [#8303](https://github.com/owncloud/ocis/pull/8303) * Bugfix - Fix concurrent shares config: [#8317](https://github.com/owncloud/ocis/pull/8317) +* Bugfix - Fix Content-Disposition header for downloads: [#8381](https://github.com/owncloud/ocis/pull/8381) * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) * Enhancement - Disable the password policy: [#7985](https://github.com/owncloud/ocis/pull/7985) * Enhancement - Update antivirus service: [#8062](https://github.com/owncloud/ocis/pull/8062) @@ -234,6 +235,15 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/8317 +* Bugfix - Fix Content-Disposition header for downloads: [#8381](https://github.com/owncloud/ocis/pull/8381) + + We have fixed a bug that caused downloads to fail on Chromebased browsers when + the filename contained special characters. + + https://github.com/owncloud/ocis/issues/8361 + https://github.com/owncloud/ocis/pull/8381 + https://github.com/cs3org/reva/pull/4498 + * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) Introduce a new environment variable `IDP_LOGIN_BACKGROUND_URL` that overrides From fc5a8f967bfe7329a75d6bcb689014a61df42729 Mon Sep 17 00:00:00 2001 From: Saw-jan Date: Tue, 6 Feb 2024 17:15:04 +0545 Subject: [PATCH 033/112] test: API coverage for Content-Disposition header with file having comma --- .../downloadFile.feature | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature b/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature index 7c5eb31bc..e60e23898 100644 --- a/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature +++ b/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature @@ -262,31 +262,35 @@ Feature: download file | dav-path-version | | spaces | - @smokeTest + @smokeTest @issue-8361 Scenario Outline: downloading a file should serve security headers Given using DAV path - When user "Alice" downloads file "/welcome.txt" using the WebDAV API + And user "Alice" has uploaded file with content "test file" to "/" + When user "Alice" downloads file "/" using the WebDAV API Then the HTTP status code should be "200" And the following headers should be set - | header | value | - | Content-Disposition | attachment; filename*=UTF-8''"welcome.txt"; filename="welcome.txt" | - | Content-Security-Policy | default-src 'none'; | - | X-Content-Type-Options | nosniff | - | X-Download-Options | noopen | - | X-Frame-Options | SAMEORIGIN | - | X-Permitted-Cross-Domain-Policies | none | - | X-Robots-Tag | none | - | X-XSS-Protection | 1; mode=block | - And the downloaded content should start with "Welcome" + | header | value | + | Content-Disposition | attachment; filename*=UTF-8''""; filename="" | + | Content-Security-Policy | default-src 'none'; | + | X-Content-Type-Options | nosniff | + | X-Download-Options | noopen | + | X-Frame-Options | SAMEORIGIN | + | X-Permitted-Cross-Domain-Policies | none | + | X-Robots-Tag | none | + | X-XSS-Protection | 1; mode=block | + And the downloaded content should be "test file" Examples: - | dav-path-version | - | old | - | new | + | dav-path-version | file | + | old | textfile.txt | + | old | comma,.txt | + | new | textfile.txt | + | new | comma,.txt | @skipOnRevaMaster Examples: - | dav-path-version | - | spaces | + | dav-path-version | file | + | spaces | textfile.txt | + | spaces | comma,.txt | Scenario: download a zero byte size file From 656493941f572175b7d17bee9915409fba5c5285 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 06:47:17 +0000 Subject: [PATCH 034/112] build(deps): bump go.opentelemetry.io/otel/trace from 1.22.0 to 1.23.0 Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.22.0 to 1.23.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.22.0...v1.23.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/trace dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +-- go.sum | 12 ++--- vendor/go.opentelemetry.io/otel/CHANGELOG.md | 54 ++++++++++++++++++- vendor/go.opentelemetry.io/otel/CODEOWNERS | 2 +- .../go.opentelemetry.io/otel/CONTRIBUTING.md | 2 +- .../go.opentelemetry.io/otel/attribute/set.go | 2 +- vendor/go.opentelemetry.io/otel/version.go | 2 +- vendor/go.opentelemetry.io/otel/versions.yaml | 16 +++--- vendor/modules.txt | 6 +-- 9 files changed, 76 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 59f83d8b8..4f78e427d 100644 --- a/go.mod +++ b/go.mod @@ -89,11 +89,11 @@ require ( go.etcd.io/bbolt v1.3.8 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 go.opentelemetry.io/contrib/zpages v0.47.0 - go.opentelemetry.io/otel v1.22.0 + go.opentelemetry.io/otel v1.23.0 go.opentelemetry.io/otel/exporters/jaeger v1.17.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 go.opentelemetry.io/otel/sdk v1.22.0 - go.opentelemetry.io/otel/trace v1.22.0 + go.opentelemetry.io/otel/trace v1.23.0 golang.org/x/crypto v0.18.0 golang.org/x/image v0.15.0 golang.org/x/net v0.20.0 @@ -330,7 +330,7 @@ require ( go.opentelemetry.io/contrib v1.0.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.23.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect diff --git a/go.sum b/go.sum index 70eddac68..49e67dddf 100644 --- a/go.sum +++ b/go.sum @@ -2099,22 +2099,22 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1: go.opentelemetry.io/contrib/zpages v0.47.0 h1:ekpdNa2wqOvAfwZIGDIIV02zmR+z08aWPt21KrPJnaU= go.opentelemetry.io/contrib/zpages v0.47.0/go.mod h1:rBeFA/UxnMjRlEGpmClIqzf1mCIKtl7ahjww3wsSdGs= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E= +go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo= +go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= +go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= diff --git a/vendor/go.opentelemetry.io/otel/CHANGELOG.md b/vendor/go.opentelemetry.io/otel/CHANGELOG.md index fe670d79c..ea1f723e2 100644 --- a/vendor/go.opentelemetry.io/otel/CHANGELOG.md +++ b/vendor/go.opentelemetry.io/otel/CHANGELOG.md @@ -8,6 +8,54 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +## [1.23.0] 2024-02-06 + +This release contains the first stable, `v1`, release of the following modules: + +- `go.opentelemetry.io/otel/bridge/opencensus` +- `go.opentelemetry.io/otel/bridge/opencensus/test` +- `go.opentelemetry.io/otel/example/opencensus` +- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` +- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` +- `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` + +See our [versioning policy](VERSIONING.md) for more information about these stability guarantees. + +### Added + +- Add `WithEndpointURL` option to the `exporters/otlp/otlpmetric/otlpmetricgrpc`, `exporters/otlp/otlpmetric/otlpmetrichttp`, `exporters/otlp/otlptrace/otlptracegrpc` and `exporters/otlp/otlptrace/otlptracehttp` packages. (#4808) +- Experimental exemplar exporting is added to the metric SDK. + See [metric documentation](./sdk/metric/EXPERIMENTAL.md#exemplars) for more information about this feature and how to enable it. (#4871) +- `ErrSchemaURLConflict` is added to `go.opentelemetry.io/otel/sdk/resource`. + This error is returned when a merge of two `Resource`s with different (non-empty) schema URL is attempted. (#4876) + +### Changed + +- The `Merge` and `New` functions in `go.opentelemetry.io/otel/sdk/resource` now returns a partial result if there is a schema URL merge conflict. + Instead of returning `nil` when two `Resource`s with different (non-empty) schema URLs are merged the merged `Resource`, along with the new `ErrSchemaURLConflict` error, is returned. + It is up to the user to decide if they want to use the returned `Resource` or not. + It may have desired attributes overwritten or include stale semantic conventions. (#4876) + +### Fixed + +- Fix `ContainerID` resource detection on systemd when cgroup path has a colon. (#4449) +- Fix `go.opentelemetry.io/otel/sdk/metric` to cache instruments to avoid leaking memory when the same instrument is created multiple times. (#4820) +- Fix missing `Mix` and `Max` values for `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` by introducing `MarshalText` and `MarshalJSON` for the `Extrema` type in `go.opentelemetry.io/sdk/metric/metricdata`. (#4827) + +## [1.23.0-rc.1] 2024-01-18 + +This is a release candidate for the v1.23.0 release. +That release is expected to include the `v1` release of the following modules: + +- `go.opentelemetry.io/otel/bridge/opencensus` +- `go.opentelemetry.io/otel/bridge/opencensus/test` +- `go.opentelemetry.io/otel/example/opencensus` +- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` +- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` +- `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` + +See our [versioning policy](VERSIONING.md) for more information about these stability guarantees. + ## [1.22.0/0.45.0] 2024-01-17 ### Added @@ -28,7 +76,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed - Upgrade all use of `go.opentelemetry.io/otel/semconv` to use `v1.24.0`. (#4754) -- Update transformations in `go.opentelemetry.io/otel/exporters/zipkin` to follow `v1.19.0` version of the OpenTelemetry specification. (#4754) +- Update transformations in `go.opentelemetry.io/otel/exporters/zipkin` to follow `v1.24.0` version of the OpenTelemetry specification. (#4754) - Record synchronous measurements when the passed context is canceled instead of dropping in `go.opentelemetry.io/otel/sdk/metric`. If you do not want to make a measurement when the context is cancelled, you need to handle it yourself (e.g `if ctx.Err() != nil`). (#4671) - Improve `go.opentelemetry.io/otel/trace.TraceState`'s performance. (#4722) @@ -2775,7 +2823,9 @@ It contains api and sdk for trace and meter. - CircleCI build CI manifest files. - CODEOWNERS file to track owners of this project. -[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.22.0...HEAD +[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.23.0...HEAD +[1.23.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.0 +[1.23.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.0-rc.1 [1.22.0/0.45.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.22.0 [1.21.0/0.44.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.21.0 [1.20.0/0.43.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.20.0 diff --git a/vendor/go.opentelemetry.io/otel/CODEOWNERS b/vendor/go.opentelemetry.io/otel/CODEOWNERS index 623740007..31d336d92 100644 --- a/vendor/go.opentelemetry.io/otel/CODEOWNERS +++ b/vendor/go.opentelemetry.io/otel/CODEOWNERS @@ -14,4 +14,4 @@ * @MrAlias @Aneurysm9 @evantorrie @XSAM @dashpole @MadVikingGod @pellared @hanyuancheung @dmathieu -CODEOWNERS @MrAlias @MadVikingGod @pellared \ No newline at end of file +CODEOWNERS @MrAlias @MadVikingGod @pellared @dashpole \ No newline at end of file diff --git a/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md b/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md index 31857a617..c9f2bac55 100644 --- a/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md +++ b/vendor/go.opentelemetry.io/otel/CONTRIBUTING.md @@ -617,13 +617,13 @@ should be canceled. - [Evan Torrie](https://github.com/evantorrie), Verizon Media - [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics -- [David Ashpole](https://github.com/dashpole), Google - [Chester Cheung](https://github.com/hanyuancheung), Tencent - [Damien Mathieu](https://github.com/dmathieu), Elastic - [Anthony Mirabella](https://github.com/Aneurysm9), AWS ### Maintainers +- [David Ashpole](https://github.com/dashpole), Google - [Aaron Clawson](https://github.com/MadVikingGod), LightStep - [Robert Pająk](https://github.com/pellared), Splunk - [Tyler Yahn](https://github.com/MrAlias), Splunk diff --git a/vendor/go.opentelemetry.io/otel/attribute/set.go b/vendor/go.opentelemetry.io/otel/attribute/set.go index 7e6765b06..fb6da5145 100644 --- a/vendor/go.opentelemetry.io/otel/attribute/set.go +++ b/vendor/go.opentelemetry.io/otel/attribute/set.go @@ -427,7 +427,7 @@ func (l *Set) MarshalJSON() ([]byte, error) { return json.Marshal(l.equivalent.iface) } -// MarshalLog is the marshaling function used by the logging system to represent this exporter. +// MarshalLog is the marshaling function used by the logging system to represent this Set. func (l Set) MarshalLog() interface{} { kvs := make(map[string]string) for _, kv := range l.ToSlice() { diff --git a/vendor/go.opentelemetry.io/otel/version.go b/vendor/go.opentelemetry.io/otel/version.go index c7aba1c3f..38ba951ed 100644 --- a/vendor/go.opentelemetry.io/otel/version.go +++ b/vendor/go.opentelemetry.io/otel/version.go @@ -16,5 +16,5 @@ package otel // import "go.opentelemetry.io/otel" // Version is the current release version of OpenTelemetry in use. func Version() string { - return "1.22.0" + return "1.23.0" } diff --git a/vendor/go.opentelemetry.io/otel/versions.yaml b/vendor/go.opentelemetry.io/otel/versions.yaml index a9cfb80ae..028393f07 100644 --- a/vendor/go.opentelemetry.io/otel/versions.yaml +++ b/vendor/go.opentelemetry.io/otel/versions.yaml @@ -14,19 +14,25 @@ module-sets: stable-v1: - version: v1.22.0 + version: v1.23.0 modules: - go.opentelemetry.io/otel + - go.opentelemetry.io/otel/bridge/opencensus + - go.opentelemetry.io/otel/bridge/opencensus/test - go.opentelemetry.io/otel/bridge/opentracing - go.opentelemetry.io/otel/bridge/opentracing/test - go.opentelemetry.io/otel/example/dice - go.opentelemetry.io/otel/example/namedtracer + - go.opentelemetry.io/otel/example/opencensus - go.opentelemetry.io/otel/example/otel-collector - go.opentelemetry.io/otel/example/passthrough - go.opentelemetry.io/otel/example/zipkin + - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc + - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp - go.opentelemetry.io/otel/exporters/otlp/otlptrace - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp + - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric - go.opentelemetry.io/otel/exporters/stdout/stdouttrace - go.opentelemetry.io/otel/exporters/zipkin - go.opentelemetry.io/otel/metric @@ -34,16 +40,10 @@ module-sets: - go.opentelemetry.io/otel/sdk/metric - go.opentelemetry.io/otel/trace experimental-metrics: - version: v0.45.0 + version: v0.45.1 modules: - - go.opentelemetry.io/otel/bridge/opencensus - - go.opentelemetry.io/otel/bridge/opencensus/test - - go.opentelemetry.io/otel/example/opencensus - go.opentelemetry.io/otel/example/prometheus - - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc - - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp - go.opentelemetry.io/otel/exporters/prometheus - - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric experimental-schema: version: v0.0.7 modules: diff --git a/vendor/modules.txt b/vendor/modules.txt index 9ebb835c7..d1db0b36c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1940,7 +1940,7 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvut ## explicit; go 1.20 go.opentelemetry.io/contrib/zpages go.opentelemetry.io/contrib/zpages/internal -# go.opentelemetry.io/otel v1.22.0 +# go.opentelemetry.io/otel v1.23.0 ## explicit; go 1.20 go.opentelemetry.io/otel go.opentelemetry.io/otel/attribute @@ -1976,7 +1976,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry -# go.opentelemetry.io/otel/metric v1.22.0 +# go.opentelemetry.io/otel/metric v1.23.0 ## explicit; go 1.20 go.opentelemetry.io/otel/metric go.opentelemetry.io/otel/metric/embedded @@ -1988,7 +1988,7 @@ go.opentelemetry.io/otel/sdk/internal go.opentelemetry.io/otel/sdk/internal/env go.opentelemetry.io/otel/sdk/resource go.opentelemetry.io/otel/sdk/trace -# go.opentelemetry.io/otel/trace v1.22.0 +# go.opentelemetry.io/otel/trace v1.23.0 ## explicit; go 1.20 go.opentelemetry.io/otel/trace go.opentelemetry.io/otel/trace/embedded From 0fd7f50ccbfef9fb96a6c3b33daa332827df38e2 Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Tue, 6 Feb 2024 14:40:13 +0100 Subject: [PATCH 035/112] enhancement: allow sending multiple userIDs in one SSE event --- .../enhancement-allow-multiple-event-user-ids.md | 6 ++++++ services/clientlog/pkg/service/service.go | 16 ++++++++-------- services/sse/pkg/service/service.go | 15 +++++++++------ services/userlog/pkg/service/service.go | 11 ++++++----- 4 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 changelog/unreleased/enhancement-allow-multiple-event-user-ids.md diff --git a/changelog/unreleased/enhancement-allow-multiple-event-user-ids.md b/changelog/unreleased/enhancement-allow-multiple-event-user-ids.md new file mode 100644 index 000000000..0fd033cea --- /dev/null +++ b/changelog/unreleased/enhancement-allow-multiple-event-user-ids.md @@ -0,0 +1,6 @@ +Enhancement: allow sending multiple user ids in one sse event + +Sending multiple user ids in one sse event is now possible which reduces the number of sent events. + +https://github.com/owncloud/ocis/pull/8379 +https://github.com/cs3org/reva/pull/4501 diff --git a/services/clientlog/pkg/service/service.go b/services/clientlog/pkg/service/service.go index 22b61cc0c..f905c8984 100644 --- a/services/clientlog/pkg/service/service.go +++ b/services/clientlog/pkg/service/service.go @@ -8,13 +8,15 @@ import ( "reflect" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + "go.opentelemetry.io/otel/trace" + "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/clientlog/pkg/config" - "go.opentelemetry.io/otel/trace" ) // ClientlogService is the service responsible for user activities @@ -116,22 +118,20 @@ func (cl *ClientlogService) processEvent(event events.Event) { } // II) instruct sse service to send the information - for _, id := range users { - if err := cl.sendSSE(id, evType, data); err != nil { - cl.log.Error().Err(err).Str("userID", id).Str("eventid", event.ID).Msg("failed to store event for user") - return - } + if err := cl.sendSSE(users, evType, data); err != nil { + cl.log.Error().Err(err).Interface("userIDs", users).Str("eventid", event.ID).Msg("failed to store event for user") + return } } -func (cl *ClientlogService) sendSSE(userid string, evType string, data interface{}) error { +func (cl *ClientlogService) sendSSE(userIDs []string, evType string, data interface{}) error { b, err := json.Marshal(data) if err != nil { return err } return events.Publish(context.Background(), cl.publisher, events.SendSSE{ - UserID: userid, + UserIDs: userIDs, Type: evType, Message: b, }) diff --git a/services/sse/pkg/service/service.go b/services/sse/pkg/service/service.go index 23979d866..aac0cdd42 100644 --- a/services/sse/pkg/service/service.go +++ b/services/sse/pkg/service/service.go @@ -3,11 +3,12 @@ package service import ( "net/http" - revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/events" "github.com/go-chi/chi/v5" "github.com/r3labs/sse/v2" + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/sse/pkg/config" ) @@ -51,10 +52,12 @@ func (s SSE) ListenForEvents() { default: s.l.Error().Interface("event", ev).Msg("unhandled event") case events.SendSSE: - s.sse.Publish(ev.UserID, &sse.Event{ - Event: []byte(ev.Type), - Data: ev.Message, - }) + for _, uid := range ev.UserIDs { + s.sse.Publish(uid, &sse.Event{ + Event: []byte(ev.Type), + Data: ev.Message, + }) + } } } } diff --git a/services/userlog/pkg/service/service.go b/services/userlog/pkg/service/service.go index 684b9c8f3..3dba5caca 100644 --- a/services/userlog/pkg/service/service.go +++ b/services/userlog/pkg/service/service.go @@ -10,10 +10,14 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/go-chi/chi/v5" + micrometadata "go-micro.dev/v4/metadata" + "go-micro.dev/v4/store" + "go.opentelemetry.io/otel/trace" + "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" - "github.com/go-chi/chi/v5" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/middleware" "github.com/owncloud/ocis/v2/ocis-pkg/roles" @@ -22,9 +26,6 @@ import ( settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" "github.com/owncloud/ocis/v2/services/userlog/pkg/config" - micrometadata "go-micro.dev/v4/metadata" - "go-micro.dev/v4/store" - "go.opentelemetry.io/otel/trace" ) // UserlogService is the service responsible for user activities @@ -348,7 +349,7 @@ func (ul *UserlogService) sendSSE(ctx context.Context, userid string, event even } return events.Publish(context.Background(), ul.publisher, events.SendSSE{ - UserID: userid, + UserIDs: []string{userid}, Type: "userlog-notification", Message: b, }) From 6487ed7101c3f1f4dfacfd54692bc2025ceadd25 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Tue, 6 Feb 2024 14:00:19 +0100 Subject: [PATCH 036/112] userlog: send only one SSEEvent per language Signed-off-by: jkoberg --- services/userlog/pkg/service/service.go | 70 +++++++++++++++++-------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/services/userlog/pkg/service/service.go b/services/userlog/pkg/service/service.go index 3dba5caca..696055de2 100644 --- a/services/userlog/pkg/service/service.go +++ b/services/userlog/pkg/service/service.go @@ -190,16 +190,18 @@ func (ul *UserlogService) processEvent(event events.Event) { // III) store the eventID for each user for _, id := range users { - if !ul.cfg.DisableSSE { - if err := ul.sendSSE(ctx, id, event, gwc); err != nil { - ul.log.Error().Err(err).Str("userid", id).Str("eventid", event.ID).Msg("cannot create sse event") - } - } - if err := ul.addEventToUser(ctx, id, event); err != nil { + if err := ul.addEventToUser(id, event); err != nil { ul.log.Error().Err(err).Str("userID", id).Str("eventid", event.ID).Msg("failed to store event for user") return } } + + // IV) send sses + if !ul.cfg.DisableSSE { + if err := ul.sendSSE(ctx, users, event, gwc); err != nil { + ul.log.Error().Err(err).Interface("userid", users).Str("eventid", event.ID).Msg("cannot create sse event") + } + } } // GetEvents allows retrieving events from the eventhistory by userid @@ -230,12 +232,12 @@ func (ul *UserlogService) GetEvents(ctx context.Context, userid string) ([]*ehms // remove expired events from list asynchronously go func() { - if err := ul.removeExpiredEvents(userid, eventIDs, resp.Events); err != nil { + if err := ul.removeExpiredEvents(userid, eventIDs, resp.GetEvents()); err != nil { ul.log.Error().Err(err).Str("userid", userid).Msg("could not remove expired events from user") } }() - return resp.Events, nil + return resp.GetEvents(), nil } // DeleteEvents will delete the specified events @@ -248,7 +250,7 @@ func (ul *UserlogService) DeleteEvents(userid string, evids []string) error { return ul.alterUserEventList(userid, func(ids []string) []string { var newids []string for _, id := range ids { - if _, delete := toDelete[id]; delete { + if _, del := toDelete[id]; del { continue } @@ -331,28 +333,52 @@ func (ul *UserlogService) DeleteGlobalEvents(ctx context.Context, evnames []stri }) } -func (ul *UserlogService) addEventToUser(ctx context.Context, userid string, event events.Event) error { +func (ul *UserlogService) addEventToUser(userid string, event events.Event) error { return ul.alterUserEventList(userid, func(ids []string) []string { return append(ids, event.ID) }) } -func (ul *UserlogService) sendSSE(ctx context.Context, userid string, event events.Event, gwc gateway.GatewayAPIClient) error { - ev, err := NewConverter(ctx, ul.getUserLocale(userid), gwc, ul.cfg.Service.Name, ul.cfg.TranslationPath, ul.cfg.DefaultLanguage).ConvertEvent(event.ID, event.Event) - if err != nil { - return err +func (ul *UserlogService) sendSSE(ctx context.Context, userIDs []string, event events.Event, gwc gateway.GatewayAPIClient) error { + m := make(map[string]events.SendSSE) + + for _, userid := range userIDs { + loc := ul.getUserLocale(userid) + if ev, ok := m[loc]; ok { + ev.UserIDs = append(m[loc].UserIDs, userid) + m[loc] = ev + continue + } + + ev, err := NewConverter(ctx, loc, gwc, ul.cfg.Service.Name, ul.cfg.TranslationPath, ul.cfg.DefaultLanguage).ConvertEvent(event.ID, event.Event) + if err != nil { + if utils.IsErrNotFound(err) || utils.IsErrPermissionDenied(err) { + // the resource was not found, we assume it is deleted + continue + } + return err + } + + b, err := json.Marshal(ev) + if err != nil { + return err + } + + m[loc] = events.SendSSE{ + UserIDs: []string{userid}, + Type: "userlog-notification", + Message: b, + } + } - b, err := json.Marshal(ev) - if err != nil { - return err + for _, ev := range m { + if err := events.Publish(ctx, ul.publisher, ev); err != nil { + return err + } } - return events.Publish(context.Background(), ul.publisher, events.SendSSE{ - UserIDs: []string{userid}, - Type: "userlog-notification", - Message: b, - }) + return nil } func (ul *UserlogService) removeExpiredEvents(userid string, all []string, received []*ehmsg.Event) error { From c851490399b0088399fa1a8efe6949cfc4333ddf Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Tue, 6 Feb 2024 14:55:13 +0100 Subject: [PATCH 037/112] chore: bump reva --- go.mod | 2 +- go.sum | 4 ++-- vendor/github.com/cs3org/reva/v2/pkg/events/sse.go | 4 ++-- .../storage/utils/decomposedfs/spaceidindex/spaceidindex.go | 6 ++++++ vendor/modules.txt | 2 +- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4f78e427d..acd2f23ea 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/blevesearch/bleve/v2 v2.3.10 github.com/coreos/go-oidc/v3 v3.9.0 github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 - github.com/cs3org/reva/v2 v2.18.1-0.20240206122233-bf89f7aaedd1 + github.com/cs3org/reva/v2 v2.18.1-0.20240206135152-2343fe56d568 github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 github.com/disintegration/imaging v1.6.2 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e diff --git a/go.sum b/go.sum index 49e67dddf..2baf824e0 100644 --- a/go.sum +++ b/go.sum @@ -1019,8 +1019,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= -github.com/cs3org/reva/v2 v2.18.1-0.20240206122233-bf89f7aaedd1 h1:74SlyiFIUYcTvSFaEyeyABg3nuiDko91ve0fE++t87s= -github.com/cs3org/reva/v2 v2.18.1-0.20240206122233-bf89f7aaedd1/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= +github.com/cs3org/reva/v2 v2.18.1-0.20240206135152-2343fe56d568 h1:RDytVxpVjcJNLQM34yFvHkxnysVQJ0Sa4k4vuI1SH5o= +github.com/cs3org/reva/v2 v2.18.1-0.20240206135152-2343fe56d568/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= diff --git a/vendor/github.com/cs3org/reva/v2/pkg/events/sse.go b/vendor/github.com/cs3org/reva/v2/pkg/events/sse.go index 34841c81f..fa3459d9f 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/events/sse.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/events/sse.go @@ -4,9 +4,9 @@ import ( "encoding/json" ) -// SendSEE instructs the sse service to send a notification to a user +// SendSSE instructs the sse service to send one or multiple notifications type SendSSE struct { - UserID string + UserIDs []string Type string Message []byte } diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaceidindex/spaceidindex.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaceidindex/spaceidindex.go index 4d9add389..d9dc9c359 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaceidindex/spaceidindex.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaceidindex/spaceidindex.go @@ -50,10 +50,16 @@ func (i *Index) Load(index string) (map[string]string, error) { } // Add adds an entry to an index +// Consider calling AddAll() when trying to add multiple entries as every Add call has to lock the index func (i *Index) Add(index, key string, value string) error { return i.updateIndex(index, map[string]string{key: value}, []string{}) } +// AddAll adds multiple entries to the index +func (i *Index) AddAll(index string, m map[string]string) error { + return i.updateIndex(index, m, []string{}) +} + // Remove removes an entry from the index func (i *Index) Remove(index, key string) error { return i.updateIndex(index, map[string]string{}, []string{key}) diff --git a/vendor/modules.txt b/vendor/modules.txt index d1db0b36c..354388571 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -362,7 +362,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1 github.com/cs3org/go-cs3apis/cs3/types/v1beta1 -# github.com/cs3org/reva/v2 v2.18.1-0.20240206122233-bf89f7aaedd1 +# github.com/cs3org/reva/v2 v2.18.1-0.20240206135152-2343fe56d568 ## explicit; go 1.21 github.com/cs3org/reva/v2/cmd/revad/internal/grace github.com/cs3org/reva/v2/cmd/revad/runtime From e66a1d62d2054ec064fb32520bc900cc5b08e96d Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 7 Feb 2024 09:52:49 +0100 Subject: [PATCH 038/112] [docs-only] Fix unrelease text --- changelog/unreleased/fix-cs3-backend.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/unreleased/fix-cs3-backend.md b/changelog/unreleased/fix-cs3-backend.md index ec72befa0..1dca57590 100644 --- a/changelog/unreleased/fix-cs3-backend.md +++ b/changelog/unreleased/fix-cs3-backend.md @@ -1,5 +1,5 @@ -Bugfix: IDP CS3 backend sessions now survire restart +Bugfix: IDP CS3 backend sessions now survive a restart -We now correctly reinitialize the CS3 backend session after the IDP has been restarted. +We now correctly reinitialize the CS3 backend session after the IDP service has been restarted. https://github.com/owncloud/ocis/pull/8142 From dcc17b399488387ed106ff437230a384cf594b93 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 7 Feb 2024 08:59:05 +0000 Subject: [PATCH 039/112] Automated changelog update [skip ci] --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca97c12c3..c93ac78bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ The following sections list the changes for unreleased. * Bugfix - Fix the upload postprocessing: [#8117](https://github.com/owncloud/ocis/pull/8117) * Bugfix - Disallow to delete a file during the processing: [#8132](https://github.com/owncloud/ocis/pull/8132) * Bugfix - Fix wrong naming in nats-js-kv registry: [#8140](https://github.com/owncloud/ocis/pull/8140) -* Bugfix - IDP CS3 backend sessions now survire restart: [#8142](https://github.com/owncloud/ocis/pull/8142) +* Bugfix - IDP CS3 backend sessions now survive a restart: [#8142](https://github.com/owncloud/ocis/pull/8142) * Bugfix - Fix patching of language: [#8182](https://github.com/owncloud/ocis/pull/8182) * Bugfix - Fix search service to not log expected cases as errors: [#8200](https://github.com/owncloud/ocis/pull/8200) * Bugfix - Updating and reset logo failed: [#8211](https://github.com/owncloud/ocis/pull/8211) @@ -116,10 +116,10 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/8140 -* Bugfix - IDP CS3 backend sessions now survire restart: [#8142](https://github.com/owncloud/ocis/pull/8142) +* Bugfix - IDP CS3 backend sessions now survive a restart: [#8142](https://github.com/owncloud/ocis/pull/8142) - We now correctly reinitialize the CS3 backend session after the IDP has been - restarted. + We now correctly reinitialize the CS3 backend session after the IDP service has + been restarted. https://github.com/owncloud/ocis/pull/8142 From 892660433f40eae5e8bc85e46fbfac58524614a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:50:45 +0000 Subject: [PATCH 040/112] build(deps): bump github.com/nats-io/nats-server/v2 Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.10.9 to 2.10.10. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.10.9...v2.10.10) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 +- go.sum | 8 +- .../klauspost/compress/flate/deflate.go | 2 +- .../compress/internal/race/norace.go | 13 + .../klauspost/compress/internal/race/race.go | 26 + .../klauspost/compress/s2/decode.go | 6 + .../klauspost/compress/s2/encode_amd64.go | 14 + .../klauspost/compress/s2/reader.go | 16 +- vendor/github.com/klauspost/compress/s2/s2.go | 4 + .../klauspost/compress/s2/writer.go | 14 +- .../nats-io/nats-server/v2/server/accounts.go | 4 + .../nats-server/v2/server/auth_callout.go | 9 +- .../nats-io/nats-server/v2/server/client.go | 30 +- .../nats-io/nats-server/v2/server/const.go | 2 +- .../nats-io/nats-server/v2/server/consumer.go | 30 +- .../nats-io/nats-server/v2/server/events.go | 4 +- .../nats-server/v2/server/filestore.go | 459 +++++++++++------- .../nats-server/v2/server/jetstream.go | 10 +- .../nats-server/v2/server/jetstream_api.go | 4 +- .../v2/server/jetstream_cluster.go | 104 ++-- .../nats-server/v2/server/jetstream_events.go | 2 +- .../nats-io/nats-server/v2/server/memstore.go | 137 +++--- .../nats-io/nats-server/v2/server/mqtt.go | 33 +- .../nats-io/nats-server/v2/server/opts.go | 9 + .../nats-io/nats-server/v2/server/raft.go | 51 +- .../nats-io/nats-server/v2/server/route.go | 10 +- .../nats-io/nats-server/v2/server/stream.go | 22 +- .../nats-server/v2/server/stree/dump.go | 68 +++ .../nats-server/v2/server/stree/leaf.go | 50 ++ .../nats-server/v2/server/stree/node.go | 45 ++ .../nats-server/v2/server/stree/node16.go | 121 +++++ .../nats-server/v2/server/stree/node256.go | 97 ++++ .../nats-server/v2/server/stree/node4.go | 116 +++++ .../nats-server/v2/server/stree/parts.go | 134 +++++ .../nats-server/v2/server/stree/stree.go | 380 +++++++++++++++ .../nats-server/v2/server/stree/util.go | 61 +++ vendor/modules.txt | 6 +- 37 files changed, 1766 insertions(+), 339 deletions(-) create mode 100644 vendor/github.com/klauspost/compress/internal/race/norace.go create mode 100644 vendor/github.com/klauspost/compress/internal/race/race.go create mode 100644 vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go create mode 100644 vendor/github.com/nats-io/nats-server/v2/server/stree/leaf.go create mode 100644 vendor/github.com/nats-io/nats-server/v2/server/stree/node.go create mode 100644 vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go create mode 100644 vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go create mode 100644 vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go create mode 100644 vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go create mode 100644 vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go create mode 100644 vendor/github.com/nats-io/nats-server/v2/server/stree/util.go diff --git a/go.mod b/go.mod index 4f78e427d..2537f1cff 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/mna/pigeon v1.2.1 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 - github.com/nats-io/nats-server/v2 v2.10.9 + github.com/nats-io/nats-server/v2 v2.10.10 github.com/nats-io/nats.go v1.32.0 github.com/oklog/run v1.1.0 github.com/olekukonko/tablewriter v0.0.5 @@ -249,7 +249,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/juliangruber/go-intersect v1.1.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.5 // indirect github.com/klauspost/cpuid/v2 v2.1.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/libregraph/oidc-go v1.0.0 // indirect diff --git a/go.sum b/go.sum index 49e67dddf..be0d8686f 100644 --- a/go.sum +++ b/go.sum @@ -1588,8 +1588,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.5 h1:d4vBd+7CHydUqpFBgUEKkSdtSugf9YFmSkvUYPquI5E= +github.com/klauspost/compress v1.17.5/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -1745,8 +1745,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= github.com/nats-io/jwt/v2 v2.5.3 h1:/9SWvzc6hTfamcgXJ3uYRpgj+QuY2aLNqRiqrKcrpEo= github.com/nats-io/jwt/v2 v2.5.3/go.mod h1:iysuPemFcc7p4IoYots3IuELSI4EDe9Y0bQMe+I3Bf4= -github.com/nats-io/nats-server/v2 v2.10.9 h1:VEW43Zz+p+9lARtiPM9ctd6ckun+92ZT2T17HWtwiFI= -github.com/nats-io/nats-server/v2 v2.10.9/go.mod h1:oorGiV9j3BOLLO3ejQe+U7pfAGyPo+ppD7rpgNF6KTQ= +github.com/nats-io/nats-server/v2 v2.10.10 h1:g1Wd64J5SGsoqWSx1qoNu9/At7a2x+jE7Qtf2XpEx/I= +github.com/nats-io/nats-server/v2 v2.10.10/go.mod h1:/TE61Dos8NlwZnjzyE3ZlOnM6dgl7tf937dnf4VclrA= github.com/nats-io/nats.go v1.32.0 h1:Bx9BZS+aXYlxW08k8Gd3yR2s73pV5XSoAQUyp1Kwvp0= github.com/nats-io/nats.go v1.32.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= diff --git a/vendor/github.com/klauspost/compress/flate/deflate.go b/vendor/github.com/klauspost/compress/flate/deflate.go index de912e187..66d1657d2 100644 --- a/vendor/github.com/klauspost/compress/flate/deflate.go +++ b/vendor/github.com/klauspost/compress/flate/deflate.go @@ -212,7 +212,7 @@ func (d *compressor) writeBlockSkip(tok *tokens, index int, eof bool) error { // Should only be used after a start/reset. func (d *compressor) fillWindow(b []byte) { // Do not fill window if we are in store-only or huffman mode. - if d.level <= 0 { + if d.level <= 0 && d.level > -MinCustomWindowSize { return } if d.fast != nil { diff --git a/vendor/github.com/klauspost/compress/internal/race/norace.go b/vendor/github.com/klauspost/compress/internal/race/norace.go new file mode 100644 index 000000000..affbbbb59 --- /dev/null +++ b/vendor/github.com/klauspost/compress/internal/race/norace.go @@ -0,0 +1,13 @@ +// Copyright 2015 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. + +//go:build !race + +package race + +func ReadSlice[T any](s []T) { +} + +func WriteSlice[T any](s []T) { +} diff --git a/vendor/github.com/klauspost/compress/internal/race/race.go b/vendor/github.com/klauspost/compress/internal/race/race.go new file mode 100644 index 000000000..f5e240dcd --- /dev/null +++ b/vendor/github.com/klauspost/compress/internal/race/race.go @@ -0,0 +1,26 @@ +// Copyright 2015 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. + +//go:build race + +package race + +import ( + "runtime" + "unsafe" +) + +func ReadSlice[T any](s []T) { + if len(s) == 0 { + return + } + runtime.RaceReadRange(unsafe.Pointer(&s[0]), len(s)*int(unsafe.Sizeof(s[0]))) +} + +func WriteSlice[T any](s []T) { + if len(s) == 0 { + return + } + runtime.RaceWriteRange(unsafe.Pointer(&s[0]), len(s)*int(unsafe.Sizeof(s[0]))) +} diff --git a/vendor/github.com/klauspost/compress/s2/decode.go b/vendor/github.com/klauspost/compress/s2/decode.go index 6c7feafcc..264ffd0a9 100644 --- a/vendor/github.com/klauspost/compress/s2/decode.go +++ b/vendor/github.com/klauspost/compress/s2/decode.go @@ -10,6 +10,8 @@ import ( "errors" "fmt" "strconv" + + "github.com/klauspost/compress/internal/race" ) var ( @@ -63,6 +65,10 @@ func Decode(dst, src []byte) ([]byte, error) { } else { dst = make([]byte, dLen) } + + race.WriteSlice(dst) + race.ReadSlice(src[s:]) + if s2Decode(dst, src[s:]) != 0 { return nil, ErrCorrupt } diff --git a/vendor/github.com/klauspost/compress/s2/encode_amd64.go b/vendor/github.com/klauspost/compress/s2/encode_amd64.go index ebc332ad5..4f45206a4 100644 --- a/vendor/github.com/klauspost/compress/s2/encode_amd64.go +++ b/vendor/github.com/klauspost/compress/s2/encode_amd64.go @@ -3,6 +3,8 @@ package s2 +import "github.com/klauspost/compress/internal/race" + const hasAmd64Asm = true // encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It @@ -14,6 +16,9 @@ const hasAmd64Asm = true // len(dst) >= MaxEncodedLen(len(src)) && // minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize func encodeBlock(dst, src []byte) (d int) { + race.ReadSlice(src) + race.WriteSlice(dst) + const ( // Use 12 bit table when less than... limit12B = 16 << 10 @@ -50,6 +55,9 @@ func encodeBlock(dst, src []byte) (d int) { // len(dst) >= MaxEncodedLen(len(src)) && // minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize func encodeBlockBetter(dst, src []byte) (d int) { + race.ReadSlice(src) + race.WriteSlice(dst) + const ( // Use 12 bit table when less than... limit12B = 16 << 10 @@ -86,6 +94,9 @@ func encodeBlockBetter(dst, src []byte) (d int) { // len(dst) >= MaxEncodedLen(len(src)) && // minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize func encodeBlockSnappy(dst, src []byte) (d int) { + race.ReadSlice(src) + race.WriteSlice(dst) + const ( // Use 12 bit table when less than... limit12B = 16 << 10 @@ -121,6 +132,9 @@ func encodeBlockSnappy(dst, src []byte) (d int) { // len(dst) >= MaxEncodedLen(len(src)) && // minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize func encodeBlockBetterSnappy(dst, src []byte) (d int) { + race.ReadSlice(src) + race.WriteSlice(dst) + const ( // Use 12 bit table when less than... limit12B = 16 << 10 diff --git a/vendor/github.com/klauspost/compress/s2/reader.go b/vendor/github.com/klauspost/compress/s2/reader.go index 2f01a3987..46ead58fe 100644 --- a/vendor/github.com/klauspost/compress/s2/reader.go +++ b/vendor/github.com/klauspost/compress/s2/reader.go @@ -104,12 +104,14 @@ func ReaderIgnoreStreamIdentifier() ReaderOption { // For each chunk with the ID, the callback is called with the content. // Any returned non-nil error will abort decompression. // Only one callback per ID is supported, latest sent will be used. +// You can peek the stream, triggering the callback, by doing a Read with a 0 +// byte buffer. func ReaderSkippableCB(id uint8, fn func(r io.Reader) error) ReaderOption { return func(r *Reader) error { if id < 0x80 || id > 0xfd { return fmt.Errorf("ReaderSkippableCB: Invalid id provided, must be 0x80-0xfd (inclusive)") } - r.skippableCB[id] = fn + r.skippableCB[id-0x80] = fn return nil } } @@ -128,7 +130,7 @@ type Reader struct { err error decoded []byte buf []byte - skippableCB [0x80]func(r io.Reader) error + skippableCB [0xff - 0x80]func(r io.Reader) error blockStart int64 // Uncompressed offset at start of current. index *Index @@ -201,7 +203,7 @@ func (r *Reader) readFull(p []byte, allowEOF bool) (ok bool) { // The supplied slice does not need to be the size of the read. func (r *Reader) skippable(tmp []byte, n int, allowEOF bool, id uint8) (ok bool) { if id < 0x80 { - r.err = fmt.Errorf("interbal error: skippable id < 0x80") + r.err = fmt.Errorf("internal error: skippable id < 0x80") return false } if fn := r.skippableCB[id-0x80]; fn != nil { @@ -1048,15 +1050,17 @@ func (r *Reader) ReadByte() (byte, error) { } // SkippableCB will register a callback for chunks with the specified ID. -// ID must be a Reserved skippable chunks ID, 0x80-0xfe (inclusive). +// ID must be a Reserved skippable chunks ID, 0x80-0xfd (inclusive). // For each chunk with the ID, the callback is called with the content. // Any returned non-nil error will abort decompression. // Only one callback per ID is supported, latest sent will be used. // Sending a nil function will disable previous callbacks. +// You can peek the stream, triggering the callback, by doing a Read with a 0 +// byte buffer. func (r *Reader) SkippableCB(id uint8, fn func(r io.Reader) error) error { - if id < 0x80 || id > chunkTypePadding { + if id < 0x80 || id >= chunkTypePadding { return fmt.Errorf("ReaderSkippableCB: Invalid id provided, must be 0x80-0xfe (inclusive)") } - r.skippableCB[id] = fn + r.skippableCB[id-0x80] = fn return nil } diff --git a/vendor/github.com/klauspost/compress/s2/s2.go b/vendor/github.com/klauspost/compress/s2/s2.go index dae3f731f..72bcb4945 100644 --- a/vendor/github.com/klauspost/compress/s2/s2.go +++ b/vendor/github.com/klauspost/compress/s2/s2.go @@ -37,6 +37,8 @@ package s2 import ( "bytes" "hash/crc32" + + "github.com/klauspost/compress/internal/race" ) /* @@ -112,6 +114,8 @@ var crcTable = crc32.MakeTable(crc32.Castagnoli) // crc implements the checksum specified in section 3 of // https://github.com/google/snappy/blob/master/framing_format.txt func crc(b []byte) uint32 { + race.ReadSlice(b) + c := crc32.Update(0, crcTable, b) return c>>15 | c<<17 + 0xa282ead8 } diff --git a/vendor/github.com/klauspost/compress/s2/writer.go b/vendor/github.com/klauspost/compress/s2/writer.go index 089cd36d8..bba66a876 100644 --- a/vendor/github.com/klauspost/compress/s2/writer.go +++ b/vendor/github.com/klauspost/compress/s2/writer.go @@ -13,6 +13,8 @@ import ( "io" "runtime" "sync" + + "github.com/klauspost/compress/internal/race" ) const ( @@ -271,7 +273,7 @@ func (w *Writer) AddSkippableBlock(id uint8, data []byte) (err error) { return fmt.Errorf("skippable block excessed maximum size") } var header [4]byte - chunkLen := 4 + len(data) + chunkLen := len(data) header[0] = id header[1] = uint8(chunkLen >> 0) header[2] = uint8(chunkLen >> 8) @@ -282,7 +284,7 @@ func (w *Writer) AddSkippableBlock(id uint8, data []byte) (err error) { if err = w.err(err); err != nil { return err } - if n != len(data) { + if n != len(b) { return w.err(io.ErrShortWrite) } w.written += int64(n) @@ -303,9 +305,7 @@ func (w *Writer) AddSkippableBlock(id uint8, data []byte) (err error) { if err := write(header[:]); err != nil { return err } - if err := write(data); err != nil { - return err - } + return write(data) } // Create output... @@ -385,6 +385,8 @@ func (w *Writer) EncodeBuffer(buf []byte) (err error) { buf = buf[len(uncompressed):] // Get an output buffer. obuf := w.buffers.Get().([]byte)[:len(uncompressed)+obufHeaderLen] + race.WriteSlice(obuf) + output := make(chan result) // Queue output now, so we keep order. w.output <- output @@ -393,6 +395,8 @@ func (w *Writer) EncodeBuffer(buf []byte) (err error) { } w.uncompWritten += int64(len(uncompressed)) go func() { + race.ReadSlice(uncompressed) + checksum := crc(uncompressed) // Set to uncompressed. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/accounts.go b/vendor/github.com/nats-io/nats-server/v2/server/accounts.go index b45cec6f9..216bf6623 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/accounts.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/accounts.go @@ -1877,6 +1877,10 @@ func (a *Account) addServiceImport(dest *Account, from, to string, claim *jwt.Im rt := Singleton var lat *serviceLatency + if dest == nil { + return nil, ErrMissingAccount + } + dest.mu.RLock() se := dest.getServiceExport(to) if se != nil { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go b/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go index 359914c63..6620a4e51 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go @@ -230,7 +230,6 @@ func (s *Server) processClientOrLeafCallout(c *client, opts *Options) (authorize } } } - return targetAcc, nil } @@ -270,6 +269,14 @@ func (s *Server) processClientOrLeafCallout(c *client, opts *Options) (authorize return } + // the JWT is cleared, because if in operator mode it may hold the JWT + // for the bearer token that connected to the callout if in operator mode + // the permissions are already set on the client, this prevents a decode + // on c.RegisterNKeyUser which would have wrong values + c.mu.Lock() + c.opts.JWT = _EMPTY_ + c.mu.Unlock() + // Build internal user and bind to the targeted account. nkuser := buildInternalNkeyUser(arc, allowedConnTypes, targetAcc) if err := c.RegisterNkeyUser(nkuser); err != nil { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/client.go b/vendor/github.com/nats-io/nats-server/v2/server/client.go index 4ef424cbb..348c0478f 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/client.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/client.go @@ -4089,6 +4089,12 @@ func getHeader(key string, hdr []byte) []byte { return value } +// For bytes.HasPrefix below. +var ( + jsRequestNextPreB = []byte(jsRequestNextPre) + jsDirectGetPreB = []byte(jsDirectGetPre) +) + // processServiceImport is an internal callback when a subscription matches an imported service // from another account. This includes response mappings as well. func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byte) { @@ -4110,8 +4116,7 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt var checkJS bool shouldReturn := si.invalid || acc.sl == nil if !shouldReturn && !isResponse && si.to == jsAllAPI { - subj := bytesToString(c.pa.subject) - if strings.HasPrefix(subj, jsRequestNextPre) || strings.HasPrefix(subj, jsDirectGetPre) { + if bytes.HasPrefix(c.pa.subject, jsDirectGetPreB) || bytes.HasPrefix(c.pa.subject, jsRequestNextPreB) { checkJS = true } } @@ -4790,7 +4795,11 @@ func (c *client) processPingTimer() { var sendPing bool - pingInterval := c.srv.getOpts().PingInterval + opts := c.srv.getOpts() + pingInterval := opts.PingInterval + if c.kind == ROUTER && opts.Cluster.PingInterval > 0 { + pingInterval = opts.Cluster.PingInterval + } pingInterval = adjustPingInterval(c.kind, pingInterval) now := time.Now() needRTT := c.rtt == 0 || now.Sub(c.rttStart) > DEFAULT_RTT_MEASUREMENT_INTERVAL @@ -4810,7 +4819,11 @@ func (c *client) processPingTimer() { if sendPing { // Check for violation - if c.ping.out+1 > c.srv.getOpts().MaxPingsOut { + maxPingsOut := opts.MaxPingsOut + if c.kind == ROUTER && opts.Cluster.MaxPingsOut > 0 { + maxPingsOut = opts.Cluster.MaxPingsOut + } + if c.ping.out+1 > maxPingsOut { c.Debugf("Stale Client Connection - Closing") c.enqueueProto([]byte(fmt.Sprintf(errProto, "Stale Connection"))) c.mu.Unlock() @@ -4847,7 +4860,11 @@ func (c *client) setPingTimer() { if c.srv == nil { return } - d := c.srv.getOpts().PingInterval + opts := c.srv.getOpts() + d := opts.PingInterval + if c.kind == ROUTER && opts.Cluster.PingInterval > 0 { + d = opts.Cluster.PingInterval + } d = adjustPingInterval(c.kind, d) c.ping.tmr = time.AfterFunc(d, c.processPingTimer) } @@ -5740,6 +5757,9 @@ func (c *client) setFirstPingTimer() { opts := s.getOpts() d := opts.PingInterval + if c.kind == ROUTER && opts.Cluster.PingInterval > 0 { + d = opts.Cluster.PingInterval + } if !opts.DisableShortFirstPing { if c.kind != CLIENT { if d > firstPingInterval { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/const.go b/vendor/github.com/nats-io/nats-server/v2/server/const.go index dea5c9e9b..238a2e5df 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/const.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/const.go @@ -41,7 +41,7 @@ var ( const ( // VERSION is the current version for the server. - VERSION = "2.10.9" + VERSION = "2.10.10" // PROTO is the currently supported protocol. // 0 was the original diff --git a/vendor/github.com/nats-io/nats-server/v2/server/consumer.go b/vendor/github.com/nats-io/nats-server/v2/server/consumer.go index 02a8443e7..a9ad0ebc4 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/consumer.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/consumer.go @@ -4384,8 +4384,9 @@ func (o *consumer) trackPending(sseq, dseq uint64) { o.ptmr = time.AfterFunc(o.ackWait(0), o.checkPending) } if p, ok := o.pending[sseq]; ok { + // Update timestamp but keep original consumer delivery sequence. + // So do not update p.Sequence. p.Timestamp = time.Now().UnixNano() - p.Sequence = dseq } else { o.pending[sseq] = &Pending{dseq, time.Now().UnixNano()} } @@ -4606,6 +4607,8 @@ func (o *consumer) checkPending() { o.rdq = nil o.rdqi.Empty() o.pending = nil + // Mimic behavior in processAckMsg when pending is empty. + o.adflr, o.asflr = o.dseq-1, o.sseq-1 } // Update our state if needed. @@ -4936,6 +4939,7 @@ func (o *consumer) purge(sseq uint64, slseq uint64) { // This means we can reset everything at this point. if len(o.pending) == 0 { o.pending, o.rdc = nil, nil + o.adflr, o.asflr = o.dseq-1, o.sseq-1 } // We need to remove all those being queued for redelivery under o.rdq @@ -5330,33 +5334,42 @@ func (o *consumer) isMonitorRunning() bool { return o.inMonitor } +// If we detect that our ackfloor is higher than the stream's last sequence, return this error. +var errAckFloorHigherThanLastSeq = errors.New("consumer ack floor is higher than streams last sequence") + // If we are a consumer of an interest or workqueue policy stream, process that state and make sure consistent. -func (o *consumer) checkStateForInterestStream() { - o.mu.Lock() +func (o *consumer) checkStateForInterestStream() error { + o.mu.RLock() // See if we need to process this update if our parent stream is not a limits policy stream. mset := o.mset shouldProcessState := mset != nil && o.retention != LimitsPolicy if o.closed || !shouldProcessState { - o.mu.Unlock() - return + o.mu.RUnlock() + return nil } state, err := o.store.State() - o.mu.Unlock() + o.mu.RUnlock() if err != nil { - return + return err } asflr := state.AckFloor.Stream // Protect ourselves against rolling backwards. if asflr&(1<<63) != 0 { - return + return nil } // We should make sure to update the acks. var ss StreamState mset.store.FastState(&ss) + // Check if the underlying stream's last sequence is less than our floor. + // This can happen if the stream has been reset and has not caught up yet. + if asflr > ss.LastSeq { + return errAckFloorHigherThanLastSeq + } + for seq := ss.FirstSeq; seq <= asflr; seq++ { mset.ackMsg(o, seq) } @@ -5374,4 +5387,5 @@ func (o *consumer) checkStateForInterestStream() { } } } + return nil } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/events.go b/vendor/github.com/nats-io/nats-server/v2/server/events.go index 66d744d45..391e677ca 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/events.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/events.go @@ -1625,7 +1625,6 @@ func (s *Server) shutdownEventing() { // internal send loop to exit. s.sendShutdownEvent() wg.Wait() - close(rc) s.mu.Lock() defer s.mu.Unlock() @@ -1637,6 +1636,9 @@ func (s *Server) shutdownEventing() { }) // Turn everything off here. s.sys = nil + // Make sure this is done after s.sys = nil, so that we don't + // get sends to closed channels on badly-timed config reloads. + close(rc) } // Request for our local connection count. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/filestore.go b/vendor/github.com/nats-io/nats-server/v2/server/filestore.go index 8b73d353d..861fe4dc9 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/filestore.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/filestore.go @@ -40,6 +40,7 @@ import ( "github.com/klauspost/compress/s2" "github.com/minio/highwayhash" "github.com/nats-io/nats-server/v2/server/avl" + "github.com/nats-io/nats-server/v2/server/stree" "golang.org/x/crypto/chacha20" "golang.org/x/crypto/chacha20poly1305" ) @@ -176,13 +177,14 @@ type fileStore struct { lmb *msgBlock blks []*msgBlock bim map[uint32]*msgBlock - psim map[string]*psi + psim *stree.SubjectTree[psi] tsl int adml int hh hash.Hash64 qch chan struct{} fch chan struct{} fsld chan struct{} + cmu sync.RWMutex cfs []ConsumerStore sips int dirty int @@ -382,7 +384,7 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim fs := &fileStore{ fcfg: fcfg, - psim: make(map[string]*psi), + psim: stree.NewSubjectTree[psi](), bim: make(map[uint32]*msgBlock), cfg: FileStreamInfo{Created: created, StreamConfig: cfg}, prf: prf, @@ -428,7 +430,7 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim prior := fs.state // Reset anything that could have been set from above. fs.state = StreamState{} - fs.psim, fs.tsl = make(map[string]*psi), 0 + fs.psim, fs.tsl = fs.psim.Empty(), 0 fs.bim = make(map[uint32]*msgBlock) fs.blks = nil fs.tombs = nil @@ -744,7 +746,10 @@ func (fs *fileStore) setupAEK() error { if _, err := os.Stat(keyFile); err != nil && !os.IsNotExist(err) { return err } - if err := os.WriteFile(keyFile, encrypted, defaultFilePerms); err != nil { + <-dios + err = os.WriteFile(keyFile, encrypted, defaultFilePerms) + dios <- struct{}{} + if err != nil { return err } // Set our aek. @@ -775,14 +780,20 @@ func (fs *fileStore) writeStreamMeta() error { b = fs.aek.Seal(nonce, nonce, b, nil) } - if err := os.WriteFile(meta, b, defaultFilePerms); err != nil { + <-dios + err = os.WriteFile(meta, b, defaultFilePerms) + dios <- struct{}{} + if err != nil { return err } fs.hh.Reset() fs.hh.Write(b) checksum := hex.EncodeToString(fs.hh.Sum(nil)) sum := filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFileSum) - if err := os.WriteFile(sum, []byte(checksum), defaultFilePerms); err != nil { + <-dios + err = os.WriteFile(sum, []byte(checksum), defaultFilePerms) + dios <- struct{}{} + if err != nil { return err } return nil @@ -847,7 +858,7 @@ const ( // Lock should be held. func (fs *fileStore) noTrackSubjects() bool { - return !(len(fs.psim) > 0 || len(fs.cfg.Subjects) > 0 || fs.cfg.Mirror != nil || len(fs.cfg.Sources) > 0) + return !(fs.psim.Size() > 0 || len(fs.cfg.Subjects) > 0 || fs.cfg.Mirror != nil || len(fs.cfg.Sources) > 0) } // Will init the basics for a message block. @@ -950,7 +961,7 @@ func (fs *fileStore) recoverMsgBlock(index uint32) (*msgBlock, error) { // Open up the message file, but we will try to recover from the index file. // We will check that the last checksums match. - file, err := os.Open(mb.mfn) + file, err := mb.openBlock() if err != nil { return nil, err } @@ -1174,11 +1185,16 @@ func (mb *msgBlock) convertCipher() error { // the old keyfile back. if err := fs.genEncryptionKeysForBlock(mb); err != nil { keyFile := filepath.Join(mdir, fmt.Sprintf(keyScan, mb.index)) + <-dios os.WriteFile(keyFile, ekey, defaultFilePerms) + dios <- struct{}{} return err } mb.bek.XORKeyStream(buf, buf) - if err := os.WriteFile(mb.mfn, buf, defaultFilePerms); err != nil { + <-dios + err = os.WriteFile(mb.mfn, buf, defaultFilePerms) + dios <- struct{}{} + if err != nil { return err } return nil @@ -1203,7 +1219,10 @@ func (mb *msgBlock) convertToEncrypted() error { // Undo cache from above for later. mb.cache = nil mb.bek.XORKeyStream(buf, buf) - if err := os.WriteFile(mb.mfn, buf, defaultFilePerms); err != nil { + <-dios + err = os.WriteFile(mb.mfn, buf, defaultFilePerms) + dios <- struct{}{} + if err != nil { return err } return nil @@ -1285,7 +1304,9 @@ func (mb *msgBlock) rebuildStateLocked() (*LostStreamData, []uint64, error) { if mb.mfd != nil { fd = mb.mfd } else { + <-dios fd, err = os.OpenFile(mb.mfn, os.O_RDWR, defaultFilePerms) + dios <- struct{}{} if err == nil { defer fd.Close() } @@ -1577,7 +1598,7 @@ func (fs *fileStore) recoverFullState() (rerr error) { // Check for per subject info. if numSubjects := int(readU64()); numSubjects > 0 { - fs.psim, fs.tsl = make(map[string]*psi, numSubjects), 0 + fs.psim, fs.tsl = fs.psim.Empty(), 0 for i := 0; i < numSubjects; i++ { if lsubj := int(readU64()); lsubj > 0 { if bi+lsubj > len(buf) { @@ -1588,23 +1609,23 @@ func (fs *fileStore) recoverFullState() (rerr error) { // If we have lots of subjects this will alloc for each one. // We could reference the underlying buffer, but we could guess wrong if // number of blocks is large and subjects is low, since we would reference buf. - subj := string(buf[bi : bi+lsubj]) + subj := buf[bi : bi+lsubj] // We had a bug that could cause memory corruption in the PSIM that could have gotten stored to disk. // Only would affect subjects, so do quick check. - if !isValidSubject(subj, true) { + if !isValidSubject(string(subj), true) { os.Remove(fn) fs.warn("Stream state corrupt subject detected") return errCorruptState } bi += lsubj - psi := &psi{total: readU64(), fblk: uint32(readU64())} + psi := psi{total: readU64(), fblk: uint32(readU64())} if psi.total > 1 { psi.lblk = uint32(readU64()) } else { psi.lblk = psi.fblk } - fs.psim[subj] = psi - fs.tsl += len(subj) + fs.psim.Insert(subj, psi) + fs.tsl += lsubj } } } @@ -1769,13 +1790,13 @@ func (fs *fileStore) adjustAccounting(mb, nmb *msgBlock) { fs.state.Msgs++ fs.state.Bytes += fileStoreMsgSize(sm.subj, sm.hdr, sm.msg) if len(sm.subj) > 0 && fs.psim != nil { - if info, ok := fs.psim[sm.subj]; ok { + if info, ok := fs.psim.Find(stringToBytes(sm.subj)); ok { info.total++ if nmb.index > info.lblk { info.lblk = nmb.index } } else { - fs.psim[sm.subj] = &psi{total: 1, fblk: nmb.index, lblk: nmb.index} + fs.psim.Insert(stringToBytes(sm.subj), psi{total: 1, fblk: nmb.index, lblk: nmb.index}) fs.tsl += len(sm.subj) } } @@ -1801,7 +1822,7 @@ func (fs *fileStore) adjustAccounting(mb, nmb *msgBlock) { // Grabs last checksum for the named block file. // Takes into account encryption etc. func (mb *msgBlock) lastChecksum() []byte { - f, err := os.Open(mb.mfn) + f, err := mb.openBlock() if err != nil { return nil } @@ -1843,7 +1864,9 @@ func (fs *fileStore) cleanupOldMeta() { mdir := filepath.Join(fs.fcfg.StoreDir, msgDir) fs.mu.RUnlock() + <-dios f, err := os.Open(mdir) + dios <- struct{}{} if err != nil { return } @@ -2142,7 +2165,7 @@ func (fs *fileStore) expireMsgsOnRecover() { lmb.writeTombstone(last.seq, last.ts) } // Clear any global subject state. - fs.psim, fs.tsl = make(map[string]*psi), 0 + fs.psim, fs.tsl = fs.psim.Empty(), 0 } // If we purged anything, make sure we kick flush state loop. @@ -2414,6 +2437,9 @@ func (fs *fileStore) FilteredState(sseq uint64, subj string) SimpleState { // If past the end no results. if sseq > lseq { + // Make sure we track sequences + ss.First = fs.state.FirstSeq + ss.Last = fs.state.LastSeq return ss } @@ -2458,55 +2484,42 @@ func (fs *fileStore) numFilteredPending(filter string, ss *SimpleState) { return } - tsa := [32]string{} - fsa := [32]string{} - fts := tokenizeSubjectIntoSlice(fsa[:0], filter) - start, stop := uint32(math.MaxUint32), uint32(0) - for subj, psi := range fs.psim { - if isAll { - ss.Msgs += psi.total - } else { - tts := tokenizeSubjectIntoSlice(tsa[:0], subj) - if isSubsetMatchTokenized(tts, fts) { - ss.Msgs += psi.total - // Keep track of start and stop indexes for this subject. - if psi.fblk < start { - start = psi.fblk - } - if psi.lblk > stop { - stop = psi.lblk - } + fs.psim.Match(stringToBytes(filter), func(_ []byte, psi *psi) { + ss.Msgs += psi.total + // Keep track of start and stop indexes for this subject. + if psi.fblk < start { + start = psi.fblk + } + if psi.lblk > stop { + stop = psi.lblk + } + }) + // We do need to figure out the first and last sequences. + wc := subjectHasWildcard(filter) + // Do start + mb := fs.bim[start] + if mb != nil { + _, f, _ := mb.filteredPending(filter, wc, 0) + ss.First = f + } + if ss.First == 0 { + // This is a miss. This can happen since psi.fblk is lazy, but should be very rare. + for i := start + 1; i <= stop; i++ { + mb := fs.bim[i] + if mb == nil { + continue + } + if _, f, _ := mb.filteredPending(filter, wc, 0); f > 0 { + ss.First = f + break } } } - // If not collecting all we do need to figure out the first and last sequences. - if !isAll { - wc := subjectHasWildcard(filter) - // Do start - mb := fs.bim[start] - if mb != nil { - _, f, _ := mb.filteredPending(filter, wc, 0) - ss.First = f - } - if ss.First == 0 { - // This is a miss. This can happen since psi.fblk is lazy, but should be very rare. - for i := start + 1; i <= stop; i++ { - mb := fs.bim[i] - if mb == nil { - continue - } - if _, f, _ := mb.filteredPending(filter, wc, 0); f > 0 { - ss.First = f - break - } - } - } - // Now last - if mb = fs.bim[stop]; mb != nil { - _, _, l := mb.filteredPending(filter, wc, 0) - ss.Last = l - } + // Now last + if mb = fs.bim[stop]; mb != nil { + _, _, l := mb.filteredPending(filter, wc, 0) + ss.Last = l } } @@ -2522,8 +2535,8 @@ func (fs *fileStore) SubjectsState(subject string) map[string]SimpleState { start, stop := fs.blks[0], fs.lmb // We can short circuit if not a wildcard using psim for start and stop. if !subjectHasWildcard(subject) { - info := fs.psim[subject] - if info == nil { + info, ok := fs.psim.Find(stringToBytes(subject)) + if !ok { return nil } start, stop = fs.bim[info.fblk], fs.bim[info.lblk] @@ -2604,10 +2617,14 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool) wc := subjectHasWildcard(filter) // See if filter was provided but its the only subject. - if !isAll && !wc && len(fs.psim) == 1 && fs.psim[filter] != nil { - isAll = true + if !isAll && !wc && fs.psim.Size() == 1 { + if _, ok := fs.psim.Find(stringToBytes(filter)); ok { + isAll = true + } + } + if isAll && filter == _EMPTY_ { + filter = fwcs } - // If we are isAll and have no deleted we can do a simpler calculation. if !lastPerSubject && isAll && (fs.state.LastSeq-fs.state.FirstSeq+1) == fs.state.Msgs { if sseq == 0 { @@ -2638,7 +2655,7 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool) if lastPerSubject { // If we want all and our start sequence is equal or less than first return number of subjects. if isAll && sseq <= fs.state.FirstSeq { - return uint64(len(fs.psim)), validThrough + return uint64(fs.psim.Size()), validThrough } // If we are here we need to scan. We are going to scan the PSIM looking for lblks that are >= seqStart. // This will build up a list of all subjects from the selected block onward. @@ -2646,20 +2663,19 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool) mb := fs.blks[seqStart] bi := mb.index - for subj, psi := range fs.psim { + fs.psim.Match(stringToBytes(filter), func(subj []byte, psi *psi) { // If the select blk start is greater than entry's last blk skip. if bi > psi.lblk { - continue + return } - if isMatch(subj) { - total++ - // We will track the subjects that are an exact match to the last block. - // This is needed for last block processing. - if psi.lblk == bi { - lbm[subj] = true - } + total++ + // We will track the subjects that are an exact match to the last block. + // This is needed for last block processing. + if psi.lblk == bi { + lbm[string(subj)] = true } - } + }) + // Now check if we need to inspect the seqStart block. // Grab write lock in case we need to load in msgs. mb.mu.Lock() @@ -2768,15 +2784,13 @@ func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool) // If we are here it's better to calculate totals from psim and adjust downward by scanning less blocks. // TODO(dlc) - Eventually when sublist uses generics, make this sublist driven instead. start := uint32(math.MaxUint32) - for subj, psi := range fs.psim { - if isMatch(subj) { - total += psi.total - // Keep track of start index for this subject. - if psi.fblk < start { - start = psi.fblk - } + fs.psim.Match(stringToBytes(filter), func(_ []byte, psi *psi) { + total += psi.total + // Keep track of start index for this subject. + if psi.fblk < start { + start = psi.fblk } - } + }) // See if we were asked for all, if so we are done. if sseq <= fs.state.FirstSeq { return total, validThrough @@ -2860,30 +2874,14 @@ func (fs *fileStore) SubjectsTotals(filter string) map[string]uint64 { fs.mu.RLock() defer fs.mu.RUnlock() - if len(fs.psim) == 0 { + if fs.psim.Size() == 0 { return nil } - tsa := [32]string{} - fsa := [32]string{} - fts := tokenizeSubjectIntoSlice(fsa[:0], filter) - isAll := filter == _EMPTY_ || filter == fwcs - wc := subjectHasWildcard(filter) - - isMatch := func(subj string) bool { - if !wc { - return subj == filter - } - tts := tokenizeSubjectIntoSlice(tsa[:0], subj) - return isSubsetMatchTokenized(tts, fts) - } - fst := make(map[string]uint64) - for subj, psi := range fs.psim { - if isAll || isMatch(subj) { - fst[subj] = psi.total - } - } + fs.psim.Match(stringToBytes(filter), func(subj []byte, psi *psi) { + fst[string(subj)] = psi.total + }) return fst } @@ -2970,7 +2968,10 @@ func (fs *fileStore) newMsgBlockForWrite() (*msgBlock, error) { } mb.hh = hh + <-dios mfd, err := os.OpenFile(mb.mfn, os.O_CREATE|os.O_RDWR, defaultFilePerms) + dios <- struct{}{} + if err != nil { mb.dirtyCloseWithRemove(true) return nil, fmt.Errorf("Error creating msg block file: %v", err) @@ -3014,7 +3015,10 @@ func (fs *fileStore) genEncryptionKeysForBlock(mb *msgBlock) error { if _, err := os.Stat(keyFile); err != nil && !os.IsNotExist(err) { return err } - if err := os.WriteFile(keyFile, encrypted, defaultFilePerms); err != nil { + <-dios + err = os.WriteFile(keyFile, encrypted, defaultFilePerms) + dios <- struct{}{} + if err != nil { return err } mb.kfn = keyFile @@ -3033,7 +3037,7 @@ func (fs *fileStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts in var psmc uint64 psmax := mmp > 0 && len(subj) > 0 if psmax { - if info, ok := fs.psim[subj]; ok { + if info, ok := fs.psim.Find(stringToBytes(subj)); ok { psmc = info.total } } @@ -3079,13 +3083,13 @@ func (fs *fileStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts in // Adjust top level tracking of per subject msg counts. if len(subj) > 0 && fs.psim != nil { index := fs.lmb.index - if info, ok := fs.psim[subj]; ok { + if info, ok := fs.psim.Find(stringToBytes(subj)); ok { info.total++ if index > info.lblk { info.lblk = index } } else { - fs.psim[subj] = &psi{total: 1, fblk: index, lblk: index} + fs.psim.Insert(stringToBytes(subj), psi{total: 1, fblk: index, lblk: index}) fs.tsl += len(subj) } } @@ -3112,7 +3116,8 @@ func (fs *fileStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts in if ok, _ := fs.removeMsgViaLimits(fseq); ok { // Make sure we are below the limit. if psmc--; psmc >= mmp { - for info, ok := fs.psim[subj]; ok && info.total > mmp; info, ok = fs.psim[subj] { + bsubj := stringToBytes(subj) + for info, ok := fs.psim.Find(bsubj); ok && info.total > mmp; info, ok = fs.psim.Find(bsubj) { if seq, _ := fs.firstSeqForSubj(subj); seq > 0 { if ok, _ := fs.removeMsgViaLimits(seq); !ok { break @@ -3362,7 +3367,7 @@ func (fs *fileStore) firstSeqForSubj(subj string) (uint64, error) { // See if we can optimize where we start. start, stop := fs.blks[0].index, fs.lmb.index - if info, ok := fs.psim[subj]; ok { + if info, ok := fs.psim.Find(stringToBytes(subj)); ok { start, stop = info.fblk, info.lblk } @@ -3384,7 +3389,7 @@ func (fs *fileStore) firstSeqForSubj(subj string) (uint64, error) { if ss := mb.fss[subj]; ss != nil { // Adjust first if it was not where we thought it should be. if i != start { - if info, ok := fs.psim[subj]; ok { + if info, ok := fs.psim.Find(stringToBytes(subj)); ok { info.fblk = i } } @@ -3457,19 +3462,20 @@ func (fs *fileStore) enforceMsgPerSubjectLimit(fireCallback bool) { // collect all that are not correct. needAttention := make(map[string]*psi) - for subj, psi := range fs.psim { + fs.psim.Iter(func(subj []byte, psi *psi) bool { numMsgs += psi.total if psi.total > maxMsgsPer { - needAttention[subj] = psi + needAttention[string(subj)] = psi } - } + return true + }) // We had an issue with a use case where psim (and hence fss) were correct but idx was not and was not properly being caught. // So do a quick sanity check here. If we detect a skew do a rebuild then re-check. if numMsgs != fs.state.Msgs { fs.warn("Detected skew in subject-based total (%d) vs raw total (%d), rebuilding", numMsgs, fs.state.Msgs) // Clear any global subject state. - fs.psim, fs.tsl = make(map[string]*psi), 0 + fs.psim, fs.tsl = fs.psim.Empty(), 0 for _, mb := range fs.blks { ld, _, err := mb.rebuildState() if err != nil && ld != nil { @@ -3481,11 +3487,12 @@ func (fs *fileStore) enforceMsgPerSubjectLimit(fireCallback bool) { fs.rebuildStateLocked(nil) // Need to redo blocks that need attention. needAttention = make(map[string]*psi) - for subj, psi := range fs.psim { + fs.psim.Iter(func(subj []byte, psi *psi) bool { if psi.total > maxMsgsPer { - needAttention[subj] = psi + needAttention[string(subj)] = psi } - } + return true + }) } // Collect all the msgBlks we alter. @@ -3569,13 +3576,15 @@ func (fs *fileStore) removePerSubject(subj string) { return } // We do not update sense of fblk here but will do so when we resolve during lookup. - if info, ok := fs.psim[subj]; ok { + bsubj := stringToBytes(subj) + if info, ok := fs.psim.Find(bsubj); ok { info.total-- if info.total == 1 { info.fblk = info.lblk } else if info.total == 0 { - delete(fs.psim, subj) - fs.tsl -= len(subj) + if _, ok = fs.psim.Delete(bsubj); ok { + fs.tsl -= len(subj) + } } } } @@ -3902,7 +3911,10 @@ func (mb *msgBlock) compact() { // We will write to a new file and mv/rename it in case of failure. mfn := filepath.Join(mb.fs.fcfg.StoreDir, msgDir, fmt.Sprintf(newScan, mb.index)) - if err := os.WriteFile(mfn, nbuf, defaultFilePerms); err != nil { + <-dios + err := os.WriteFile(mfn, nbuf, defaultFilePerms) + dios <- struct{}{} + if err != nil { os.Remove(mfn) return } @@ -3943,11 +3955,24 @@ func (mb *msgBlock) slotInfo(slot int) (uint32, uint32, bool, error) { // Determine record length var rl uint32 - if len(mb.cache.idx) > slot+1 { - ni := mb.cache.idx[slot+1] &^ hbit - rl = ni - ri - } else { + if slot >= len(mb.cache.idx) { rl = mb.cache.lrl + } else { + // Need to account for dbit markers in idx. + // So we will walk until we find valid idx slot to calculate rl. + for i := 1; slot+i < len(mb.cache.idx); i++ { + ni := mb.cache.idx[slot+i] &^ hbit + if ni == dbit { + continue + } + rl = ni - ri + break + } + // check if we had all trailing dbits. + // If so use len of cache buf minus ri. + if rl == 0 { + rl = uint32(len(mb.cache.buf)) - ri + } } if rl < msgHdrSize { return 0, 0, false, errBadMsg @@ -4092,7 +4117,9 @@ func (mb *msgBlock) eraseMsg(seq uint64, ri, rl int) error { // Disk if mb.cache.off+mb.cache.wp > ri { + <-dios mfd, err := os.OpenFile(mb.mfn, os.O_RDWR, defaultFilePerms) + dios <- struct{}{} if err != nil { return err } @@ -4523,7 +4550,7 @@ func (fs *fileStore) checkMsgs() *LostStreamData { fs.checkAndFlushAllBlocks() // Clear any global subject state. - fs.psim, fs.tsl = make(map[string]*psi), 0 + fs.psim, fs.tsl = fs.psim.Empty(), 0 for _, mb := range fs.blks { // Make sure encryption loaded if needed for the block. @@ -4547,7 +4574,9 @@ func (mb *msgBlock) enableForWriting(fip bool) error { if mb.mfd != nil { return nil } + <-dios mfd, err := os.OpenFile(mb.mfn, os.O_CREATE|os.O_RDWR, defaultFilePerms) + dios <- struct{}{} if err != nil { return fmt.Errorf("error opening msg block file [%q]: %v", mb.mfn, err) } @@ -4817,11 +4846,6 @@ func (fs *fileStore) writeMsgRecord(seq uint64, ts int64, subj string, hdr, msg } func (mb *msgBlock) recompressOnDiskIfNeeded() error { - // Wait for disk I/O slots to become available. This prevents us from - // running away with system resources. - <-dios - defer func() { dios <- struct{}{} }() - alg := mb.fs.fcfg.Compression mb.mu.Lock() defer mb.mu.Unlock() @@ -4835,7 +4859,10 @@ func (mb *msgBlock) recompressOnDiskIfNeeded() error { // header, in which case we do nothing. // 2. The block will be uncompressed, in which case we will compress it // and then write it back out to disk, reencrypting if necessary. + <-dios origBuf, err := os.ReadFile(origFN) + dios <- struct{}{} + if err != nil { return fmt.Errorf("failed to read original block from disk: %w", err) } @@ -4880,7 +4907,9 @@ func (mb *msgBlock) recompressOnDiskIfNeeded() error { // operation if something goes wrong), create a new temporary file. We will // write out the new block here and then swap the files around afterwards // once everything else has succeeded correctly. + <-dios tmpFD, err := os.OpenFile(tmpFN, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFilePerms) + dios <- struct{}{} if err != nil { return fmt.Errorf("failed to create temporary file: %w", err) } @@ -4970,7 +4999,7 @@ func (mb *msgBlock) ensureRawBytesLoaded() error { if mb.rbytes > 0 { return nil } - f, err := os.Open(mb.mfn) + f, err := mb.openBlock() if err != nil { return err } @@ -5042,7 +5071,11 @@ func (fs *fileStore) syncBlocks() { // Check if we need to sync. // This is done not holding any locks. if needSync { - if fd, _ := os.OpenFile(fn, os.O_RDWR, defaultFilePerms); fd != nil { + <-dios + fd, _ := os.OpenFile(fn, os.O_RDWR, defaultFilePerms) + dios <- struct{}{} + // If we have an fd. + if fd != nil { canClear := fd.Sync() == nil fd.Close() // Only clear sync flag on success. @@ -5070,7 +5103,10 @@ func (fs *fileStore) syncBlocks() { // Sync state file if we are not running with sync always. if !syncAlways { - if fd, _ := os.OpenFile(fn, os.O_RDWR, defaultFilePerms); fd != nil { + <-dios + fd, _ := os.OpenFile(fn, os.O_RDWR, defaultFilePerms) + dios <- struct{}{} + if fd != nil { fd.Sync() fd.Close() } @@ -5157,10 +5193,11 @@ func (mb *msgBlock) indexCacheBuf(buf []byte) error { var index uint32 mbFirstSeq := atomic.LoadUint64(&mb.first.seq) + mbLastSeq := atomic.LoadUint64(&mb.last.seq) // Capture beginning size of dmap. dms := uint64(mb.dmap.Size()) - idxSz := atomic.LoadUint64(&mb.last.seq) - mbFirstSeq + 1 + idxSz := mbLastSeq - mbFirstSeq + 1 if mb.cache == nil { // Approximation, may adjust below. @@ -5185,12 +5222,14 @@ func (mb *msgBlock) indexCacheBuf(buf []byte) error { } lbuf := uint32(len(buf)) + var seq uint64 for index < lbuf { if index+msgHdrSize > lbuf { return errCorruptState } hdr := buf[index : index+msgHdrSize] - rl, seq, slen := le.Uint32(hdr[0:]), le.Uint64(hdr[4:]), int(le.Uint16(hdr[20:])) + rl, slen := le.Uint32(hdr[0:]), int(le.Uint16(hdr[20:])) + seq = le.Uint64(hdr[4:]) // Clear any headers bit that could be set. rl &^= hbit @@ -5257,6 +5296,19 @@ func (mb *msgBlock) indexCacheBuf(buf []byte) error { index += rl } + // Track holes at the end of the block, these would be missed in the + // earlier loop if we've ran out of block file to look at, but should + // be easily noticed because the seq will be below the last seq from + // the index. + if seq > 0 && seq < mbLastSeq { + for dseq := seq; dseq < mbLastSeq; dseq++ { + idx = append(idx, dbit) + if dms == 0 { + mb.dmap.Insert(dseq) + } + } + } + mb.cache.buf = buf mb.cache.idx = idx mb.cache.fseq = fseq @@ -5442,6 +5494,16 @@ func (mb *msgBlock) fssLoaded() bool { return mb.fss != nil } +// Wrap openBlock for the gated semaphore processing. +// Lock should be held +func (mb *msgBlock) openBlock() (*os.File, error) { + // Gate with concurrent IO semaphore. + <-dios + f, err := os.Open(mb.mfn) + dios <- struct{}{} + return f, err +} + // Used to load in the block contents. // Lock should be held and all conditionals satisfied prior. func (mb *msgBlock) loadBlock(buf []byte) ([]byte, error) { @@ -5456,7 +5518,7 @@ func (mb *msgBlock) loadBlock(buf []byte) ([]byte, error) { } if f == nil { var err error - f, err = os.Open(mb.mfn) + f, err = mb.openBlock() if err != nil { if os.IsNotExist(err) { err = errNoBlkData @@ -5665,7 +5727,7 @@ const ( ebit = 1 << 63 // Used for marking tombstone sequences. tbit = 1 << 62 - // Used to mark a bad index as deleted. + // Used to mark an index as deleted and non-existent. dbit = 1 << 30 ) @@ -5890,7 +5952,7 @@ func (fs *fileStore) loadLast(subj string, sm *StoreMsg) (lsm *StoreMsg, err err wc := subjectHasWildcard(subj) // If literal subject check for presence. if !wc { - if info := fs.psim[subj]; info == nil { + if info, ok := fs.psim.Find(stringToBytes(subj)); !ok { return nil, ErrStoreMsgNotFound } else { start, stop = info.lblk, info.fblk @@ -5960,6 +6022,16 @@ func (fs *fileStore) LoadNextMsg(filter string, wc bool, start uint64, sm *Store start = fs.state.FirstSeq } + // If start is less than or equal to beginning of our stream, meaning our first call, + // let's check the psim to see if we can skip ahead. + if start <= fs.state.FirstSeq { + var ss SimpleState + fs.numFilteredPending(filter, &ss) + if ss.First > start { + start = ss.First + } + } + if bi, _ := fs.selectMsgBlockWithIndex(start); bi >= 0 { for i := bi; i < len(fs.blks); i++ { mb := fs.blks[i] @@ -5987,7 +6059,14 @@ func (fs *fileStore) Type() StorageType { // Returns number of subjects in this store. // Lock should be held. func (fs *fileStore) numSubjects() int { - return len(fs.psim) + return fs.psim.Size() +} + +// numConsumers uses new lock. +func (fs *fileStore) numConsumers() int { + fs.cmu.RLock() + defer fs.cmu.RUnlock() + return len(fs.cfs) } // FastState will fill in state with only the following. @@ -6006,7 +6085,7 @@ func (fs *fileStore) FastState(state *StreamState) { state.NumDeleted = 0 } } - state.Consumers = len(fs.cfs) + state.Consumers = fs.numConsumers() state.NumSubjects = fs.numSubjects() fs.mu.RUnlock() } @@ -6015,7 +6094,7 @@ func (fs *fileStore) FastState(state *StreamState) { func (fs *fileStore) State() StreamState { fs.mu.RLock() state := fs.state - state.Consumers = len(fs.cfs) + state.Consumers = fs.numConsumers() state.NumSubjects = fs.numSubjects() state.Deleted = nil // make sure. @@ -6443,7 +6522,7 @@ func (fs *fileStore) purge(fseq uint64) (uint64, error) { fs.lmb = nil fs.bim = make(map[uint32]*msgBlock) // Clear any per subject tracking. - fs.psim, fs.tsl = make(map[string]*psi), 0 + fs.psim, fs.tsl = fs.psim.Empty(), 0 // Mark dirty fs.dirty++ @@ -6454,14 +6533,25 @@ func (fs *fileStore) purge(fseq uint64) (uint64, error) { // If purge directory still exists then we need to wait // in place and remove since rename would fail. if _, err := os.Stat(pdir); err == nil { + <-dios os.RemoveAll(pdir) + dios <- struct{}{} } - os.Rename(mdir, pdir) - go os.RemoveAll(pdir) + <-dios + os.Rename(mdir, pdir) + dios <- struct{}{} + + go func() { + <-dios + os.RemoveAll(pdir) + dios <- struct{}{} + }() // Create new one. + <-dios os.MkdirAll(mdir, defaultDirPerms) + dios <- struct{}{} // Make sure we have a lmb to write to. if _, err := fs.newMsgBlockForWrite(); err != nil { @@ -6629,7 +6719,10 @@ func (fs *fileStore) Compact(seq uint64) (uint64, error) { if nbuf, err = smb.cmp.Compress(nbuf); err != nil { goto SKIP } - if err = os.WriteFile(smb.mfn, nbuf, defaultFilePerms); err != nil { + <-dios + err = os.WriteFile(smb.mfn, nbuf, defaultFilePerms) + dios <- struct{}{} + if err != nil { goto SKIP } // Make sure to remove fss state. @@ -6718,7 +6811,7 @@ func (fs *fileStore) reset() error { fs.blks, fs.lmb = nil, nil // Reset subject mappings. - fs.psim, fs.tsl = make(map[string]*psi), 0 + fs.psim, fs.tsl = fs.psim.Empty(), 0 fs.bim = make(map[uint32]*msgBlock) // If we purged anything, make sure we kick flush state loop. @@ -7005,7 +7098,7 @@ func (mb *msgBlock) recalculateFirstForSubj(subj string, startSeq uint64, ss *Si // Lock should be held. func (fs *fileStore) resetGlobalPerSubjectInfo() { // Clear any global subject state. - fs.psim, fs.tsl = make(map[string]*psi), 0 + fs.psim, fs.tsl = fs.psim.Empty(), 0 for _, mb := range fs.blks { fs.populateGlobalPerSubjectInfo(mb) } @@ -7096,13 +7189,14 @@ func (fs *fileStore) populateGlobalPerSubjectInfo(mb *msgBlock) { // Now populate psim. for subj, ss := range mb.fss { if len(subj) > 0 { - if info, ok := fs.psim[subj]; ok { + bsubj := stringToBytes(subj) + if info, ok := fs.psim.Find(bsubj); ok { info.total += ss.Msgs if mb.index > info.lblk { info.lblk = mb.index } } else { - fs.psim[subj] = &psi{total: ss.Msgs, fblk: mb.index, lblk: mb.index} + fs.psim.Insert(bsubj, psi{total: ss.Msgs, fblk: mb.index, lblk: mb.index}) fs.tsl += len(subj) } } @@ -7312,7 +7406,7 @@ func (fs *fileStore) writeFullState() error { } // For calculating size. - numSubjects := len(fs.psim) + numSubjects := fs.psim.Size() // Calculate and estimate of the uper bound on the size to avoid multiple allocations. sz := 2 + // Magic and Version @@ -7324,11 +7418,12 @@ func (fs *fileStore) writeFullState() error { binary.MaxVarintLen64 + 8 + 8 // last index + record checksum + full state checksum // Do 4k on stack if possible. - var raw [4 * 1024]byte + const ssz = 4 * 1024 var buf []byte - if sz <= cap(raw) { - buf, sz = raw[0:2:cap(raw)], cap(raw) + if sz <= ssz { + var _buf [ssz]byte + buf, sz = _buf[0:2:ssz], ssz } else { buf = make([]byte, hdrLen, sz) } @@ -7344,7 +7439,7 @@ func (fs *fileStore) writeFullState() error { // Do per subject information map if applicable. buf = binary.AppendUvarint(buf, uint64(numSubjects)) if numSubjects > 0 { - for subj, psi := range fs.psim { + fs.psim.Match([]byte(fwcs), func(subj []byte, psi *psi) { buf = binary.AppendUvarint(buf, uint64(len(subj))) buf = append(buf, subj...) buf = binary.AppendUvarint(buf, psi.total) @@ -7352,7 +7447,7 @@ func (fs *fileStore) writeFullState() error { if psi.total > 1 { buf = binary.AppendUvarint(buf, uint64(psi.lblk)) } - } + }) } // Now walk all blocks and write out first and last and optional dmap encoding. @@ -7521,11 +7616,13 @@ func (fs *fileStore) stop(writeState bool) error { // We should update the upper usage layer on a stop. cb, bytes := fs.scb, int64(fs.state.Bytes) + fs.mu.Unlock() + fs.cmu.Lock() var _cfs [256]ConsumerStore cfs := append(_cfs[:0], fs.cfs...) fs.cfs = nil - fs.mu.Unlock() + fs.cmu.Unlock() for _, o := range cfs { o.Stop() @@ -7673,9 +7770,9 @@ func (fs *fileStore) streamSnapshot(w io.WriteCloser, state *StreamState, includ } // Do consumers' state last. - fs.mu.RLock() + fs.cmu.RLock() cfs := fs.cfs - fs.mu.RUnlock() + fs.cmu.RUnlock() for _, cs := range cfs { o, ok := cs.(*consumerFileStore) @@ -8018,7 +8115,10 @@ func (fs *fileStore) ConsumerStore(name string, cfg *ConsumerConfig) (ConsumerSt // Redo the state file as well here if we have one and we can tell it was plaintext. if buf, err := os.ReadFile(o.ifn); err == nil { if _, err := decodeConsumerState(buf); err == nil { - if err := os.WriteFile(o.ifn, o.encryptState(buf), defaultFilePerms); err != nil { + <-dios + err := os.WriteFile(o.ifn, o.encryptState(buf), defaultFilePerms) + dios <- struct{}{} + if err != nil { if didCreate { os.RemoveAll(odir) } @@ -8508,7 +8608,10 @@ func (cfs *consumerFileStore) writeConsumerMeta() error { if _, err := os.Stat(keyFile); err != nil && !os.IsNotExist(err) { return err } - if err := os.WriteFile(keyFile, encrypted, defaultFilePerms); err != nil { + <-dios + err = os.WriteFile(keyFile, encrypted, defaultFilePerms) + dios <- struct{}{} + if err != nil { return err } } @@ -8524,14 +8627,21 @@ func (cfs *consumerFileStore) writeConsumerMeta() error { b = cfs.aek.Seal(nonce, nonce, b, nil) } - if err := os.WriteFile(meta, b, defaultFilePerms); err != nil { + <-dios + err = os.WriteFile(meta, b, defaultFilePerms) + dios <- struct{}{} + if err != nil { return err } cfs.hh.Reset() cfs.hh.Write(b) checksum := hex.EncodeToString(cfs.hh.Sum(nil)) sum := filepath.Join(cfs.odir, JetStreamMetaFileSum) - if err := os.WriteFile(sum, []byte(checksum), defaultFilePerms); err != nil { + + <-dios + err = os.WriteFile(sum, []byte(checksum), defaultFilePerms) + dios <- struct{}{} + if err != nil { return err } return nil @@ -8617,7 +8727,10 @@ func (o *consumerFileStore) stateWithCopyLocked(doCopy bool) (*ConsumerState, er } // Read the state in here from disk.. + <-dios buf, err := os.ReadFile(o.ifn) + dios <- struct{}{} + if err != nil && !os.IsNotExist(err) { return nil, err } @@ -8883,15 +8996,15 @@ func (o *consumerFileStore) delete(streamDeleted bool) error { } func (fs *fileStore) AddConsumer(o ConsumerStore) error { - fs.mu.Lock() - defer fs.mu.Unlock() + fs.cmu.Lock() + defer fs.cmu.Unlock() fs.cfs = append(fs.cfs, o) return nil } func (fs *fileStore) RemoveConsumer(o ConsumerStore) error { - fs.mu.Lock() - defer fs.mu.Unlock() + fs.cmu.Lock() + defer fs.cmu.Unlock() for i, cfs := range fs.cfs { if o == cfs { fs.cfs = append(fs.cfs[:i], fs.cfs[i+1:]...) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go index 6b333878a..cba11d14e 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go @@ -1,4 +1,4 @@ -// Copyright 2019-2023 The NATS Authors +// Copyright 2019-2024 The NATS Authors // 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 @@ -653,9 +653,15 @@ func (a *Account) enableAllJetStreamServiceImportsAndMappings() error { } if !a.serviceImportExists(jsAllAPI) { - if err := a.AddServiceImport(s.SystemAccount(), jsAllAPI, _EMPTY_); err != nil { + // Capture si so we can turn on implicit sharing with JetStream layer. + // Make sure to set "to" otherwise will incur performance slow down. + si, err := a.addServiceImport(s.SystemAccount(), jsAllAPI, jsAllAPI, nil) + if err != nil { return fmt.Errorf("Error setting up jetstream service imports for account: %v", err) } + a.mu.Lock() + si.share = true + a.mu.Unlock() } // Check if we have a Domain specified. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go index 76c1c2a7b..14369d6c0 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go @@ -334,8 +334,8 @@ func generateJSMappingTable(domain string) map[string]string { // JSMaxDescription is the maximum description length for streams and consumers. const JSMaxDescriptionLen = 4 * 1024 -// JSMaxMetadataLen is the maximum length for streams an consumers metadata map. -// It's calculated by summing length of all keys an values. +// JSMaxMetadataLen is the maximum length for streams and consumers metadata map. +// It's calculated by summing length of all keys and values. const JSMaxMetadataLen = 128 * 1024 // JSMaxNameLen is the maximum name lengths for streams, consumers and templates. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go index 770b9957e..e7756cd11 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 The NATS Authors +// Copyright 2020-2024 The NATS Authors // 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 @@ -432,10 +432,10 @@ func (cc *jetStreamCluster) isStreamCurrent(account, stream string) bool { } // Restart the stream in question. -// Should only be called when the stream is know in a bad state. +// Should only be called when the stream is known to be in a bad state. func (js *jetStream) restartStream(acc *Account, csa *streamAssignment) { js.mu.Lock() - cc := js.cluster + s, cc := js.srv, js.cluster if cc == nil { js.mu.Unlock() return @@ -458,9 +458,18 @@ func (js *jetStream) restartStream(acc *Account, csa *streamAssignment) { } rg.node = nil } + sinceCreation := time.Since(sa.Created) js.mu.Unlock() // Process stream assignment to recreate. + // Check that we have given system enough time to start us up. + // This will be longer than obvious, and matches consumer logic in case system very busy. + if sinceCreation < 10*time.Second { + s.Debugf("Not restarting missing stream '%s > %s', too soon since creation %v", + acc, csa.Config.Name, sinceCreation) + return + } + js.processStreamAssignment(sa) // If we had consumers assigned to this server they will be present in the copy, csa. @@ -569,13 +578,24 @@ func (js *jetStream) isConsumerHealthy(mset *stream, consumer string, ca *consum // When we try to restart we nil out the node if applicable // and reprocess the consumer assignment. restartConsumer := func() { + mset.mu.RLock() + accName, streamName := mset.acc.GetName(), mset.cfg.Name + mset.mu.RUnlock() + js.mu.Lock() + deleted := ca.deleted + // Check that we have not just been created. + if !deleted && time.Since(ca.Created) < 10*time.Second { + s.Debugf("Not restarting missing consumer '%s > %s > %s', too soon since creation %v", + accName, streamName, consumer, time.Since(ca.Created)) + js.mu.Unlock() + return + } // Make sure the node is stopped if still running. if node != nil && node.State() != Closed { node.Stop() } ca.Group.node = nil - deleted := ca.deleted js.mu.Unlock() if !deleted { js.processConsumerAssignment(ca) @@ -597,7 +617,7 @@ func (js *jetStream) isConsumerHealthy(mset *stream, consumer string, ca *consum mset.mu.RLock() accName, streamName := mset.acc.GetName(), mset.cfg.Name mset.mu.RUnlock() - s.Warnf("Detected consumer cluster node skew '%s > %s'", accName, streamName, consumer) + s.Warnf("Detected consumer cluster node skew '%s > %s > %s'", accName, streamName, consumer) node.Delete() o.deleteWithoutAdvisory() restartConsumer() @@ -1123,6 +1143,7 @@ func (js *jetStream) checkForOrphans() { for accName, jsa := range js.accounts { asa := cc.streams[accName] + jsa.mu.RLock() for stream, mset := range jsa.streams { if sa := asa[stream]; sa == nil { streams = append(streams, mset) @@ -1136,6 +1157,7 @@ func (js *jetStream) checkForOrphans() { } } } + jsa.mu.RUnlock() } js.mu.Unlock() @@ -1356,7 +1378,7 @@ func (js *jetStream) monitorCluster() { case isLeader = <-lch: // For meta layer synchronize everyone to our state on becoming leader. - if isLeader { + if isLeader && n.ApplyQ().len() == 0 { n.SendSnapshot(js.metaSnapshot()) } // Process the change. @@ -2233,7 +2255,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps if err := n.InstallSnapshot(mset.stateSnapshot()); err == nil { lastState, lastSnapTime = curState, time.Now() - } else if err != errNoSnapAvailable && err != errNodeClosed { + } else if err != errNoSnapAvailable && err != errNodeClosed && err != errCatchupsRunning { s.RateLimitWarnf("Failed to install snapshot for '%s > %s' [%s]: %v", mset.acc.Name, mset.name(), n.Group(), err) } } @@ -2298,7 +2320,11 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps if mset.numConsumers() >= numExpectedConsumers { break } - time.Sleep(sleepTime) + select { + case <-s.quitCh: + return + case <-time.After(sleepTime): + } } if actual := mset.numConsumers(); actual < numExpectedConsumers { s.Warnf("All consumers not online for '%s > %s': expected %d but only have %d", accName, mset.name(), numExpectedConsumers, actual) @@ -2311,7 +2337,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps // We can arrive here NOT being the leader, so we send the snapshot only if we are, and in this case // reset the notion that we need to send the snapshot. If we are not, then the first time the server // will switch to leader (in the loop below), we will send the snapshot. - if sendSnapshot && isLeader && mset != nil && n != nil { + if sendSnapshot && isLeader && mset != nil && n != nil && !isRecovering { n.SendSnapshot(mset.stateSnapshot()) sendSnapshot = false } @@ -2331,9 +2357,14 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps // No special processing needed for when we are caught up on restart. if ce == nil { isRecovering = false - // Check on startup if we should snapshot/compact. - if _, b := n.Size(); b > compactSizeMin || n.NeedSnapshot() { - doSnapshot() + // Make sure we create a new snapshot in case things have changed such that any existing + // snapshot may no longer be valid. + doSnapshot() + // If we became leader during this time and we need to send a snapshot to our + // followers, i.e. as a result of a scale-up from R1, do it now. + if sendSnapshot && isLeader && mset != nil && n != nil { + n.SendSnapshot(mset.stateSnapshot()) + sendSnapshot = false } continue } @@ -2374,7 +2405,9 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps case isLeader = <-lch: if isLeader { - if mset != nil && n != nil && sendSnapshot { + if mset != nil && n != nil && sendSnapshot && !isRecovering { + // If we *are* recovering at the time then this will get done when the apply queue + // handles the nil guard to show the catchup ended. n.SendSnapshot(mset.stateSnapshot()) sendSnapshot = false } @@ -2841,6 +2874,19 @@ func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isReco // Process the actual message here. if err := mset.processJetStreamMsg(subject, reply, hdr, msg, lseq, ts); err != nil { + if err == errLastSeqMismatch { + var state StreamState + mset.store.FastState(&state) + // If we have no msgs and the other side is delivering us a sequence past where we + // should be reset. This is possible if the other side has a stale snapshot and no longer + // has those messages. So compact and retry to reset. + if state.Msgs == 0 { + mset.store.Compact(lseq + 1) + // Retry + err = mset.processJetStreamMsg(subject, reply, hdr, msg, lseq, ts) + } + } + // Only return in place if we are going to reset our stream or we are out of space, or we are closed. if isClusterResetErr(err) || isOutOfSpaceErr(err) || err == errStreamClosed { return err @@ -4086,6 +4132,7 @@ func (js *jetStream) processConsumerRemoval(ca *consumerAssignment) { // Make sure this removal is for what we have, otherwise ignore. if ca.Group != nil && oca.Group != nil && ca.Group.Name == oca.Group.Name { needDelete = true + oca.deleted = true delete(sa.consumers, ca.Name) } } @@ -4366,7 +4413,6 @@ func (js *jetStream) processClusterDeleteConsumer(ca *consumerAssignment, isMemb recovering := ca.recovering js.mu.RUnlock() - stopped := false var resp = JSApiConsumerDeleteResponse{ApiResponse: ApiResponse{Type: JSApiConsumerDeleteResponseType}} var err error var acc *Account @@ -4376,9 +4422,13 @@ func (js *jetStream) processClusterDeleteConsumer(ca *consumerAssignment, isMemb if mset, _ := acc.lookupStream(ca.Stream); mset != nil { if o := mset.lookupConsumer(ca.Name); o != nil { err = o.stopWithFlags(true, false, true, wasLeader) - stopped = true } } + } else if ca.Group != nil { + // We have a missing account, see if we can cleanup. + if sacc := s.SystemAccount(); sacc != nil { + os.RemoveAll(filepath.Join(js.config.StoreDir, sacc.GetName(), defaultStoreDirName, ca.Group.Name)) + } } // Always delete the node if present. @@ -4386,15 +4436,6 @@ func (js *jetStream) processClusterDeleteConsumer(ca *consumerAssignment, isMemb node.Delete() } - // This is a stop gap cleanup in case - // 1) the account does not exist (and mset consumer couldn't be stopped) and/or - // 2) node was nil (and couldn't be deleted) - if !stopped || node == nil { - if sacc := s.SystemAccount(); sacc != nil { - os.RemoveAll(filepath.Join(js.config.StoreDir, sacc.GetName(), defaultStoreDirName, ca.Group.Name)) - } - } - if !wasLeader || ca.Reply == _EMPTY_ { if !(offline && isMetaLeader) { return @@ -4595,8 +4636,8 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) { if !bytes.Equal(hash[:], lastSnap) || ne >= compactNumMin || nb >= compactSizeMin { if err := n.InstallSnapshot(snap); err == nil { lastSnap, lastSnapTime = hash[:], time.Now() - } else if err != errNoSnapAvailable && err != errNodeClosed { - s.Warnf("Failed to install snapshot for '%s > %s > %s' [%s]: %v", o.acc.Name, ca.Stream, ca.Name, n.Group(), err) + } else if err != errNoSnapAvailable && err != errNodeClosed && err != errCatchupsRunning { + s.RateLimitWarnf("Failed to install snapshot for '%s > %s > %s' [%s]: %v", o.acc.Name, ca.Stream, ca.Name, n.Group(), err) } } } @@ -4658,16 +4699,6 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) { js.setConsumerAssignmentRecovering(ca) } - // Synchronize everyone to our state. - if isLeader && n != nil { - // Only send out if we have state. - if _, _, applied := n.Progress(); applied > 0 { - if snap, err := o.store.EncodedState(); err == nil { - n.SendSnapshot(snap) - } - } - } - // Process the change. if err := js.processConsumerLeaderChange(o, isLeader); err == nil && isLeader { doSnapshot(true) @@ -7789,6 +7820,7 @@ func (mset *stream) processSnapshot(snap *StreamReplicatedState) (e error) { mset.store.FastState(&state) mset.setCLFS(snap.Failed) sreq := mset.calculateSyncRequest(&state, snap) + s, js, subject, n := mset.srv, mset.js, mset.sa.Sync, mset.node qname := fmt.Sprintf("[ACC:%s] stream '%s' snapshot", mset.acc.Name, mset.cfg.Name) mset.mu.Unlock() diff --git a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_events.go b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_events.go index ab7c61915..7ea14ef85 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/jetstream_events.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/jetstream_events.go @@ -18,7 +18,7 @@ import ( "time" ) -func (s *Server) publishAdvisory(acc *Account, subject string, adv interface{}) { +func (s *Server) publishAdvisory(acc *Account, subject string, adv any) { if acc == nil { acc = s.SystemAccount() if acc == nil { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/memstore.go b/vendor/github.com/nats-io/nats-server/v2/server/memstore.go index 8d324d01f..6abf0f005 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/memstore.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/memstore.go @@ -22,6 +22,7 @@ import ( "time" "github.com/nats-io/nats-server/v2/server/avl" + "github.com/nats-io/nats-server/v2/server/stree" ) // TODO(dlc) - This is a fairly simplistic approach but should do for now. @@ -30,7 +31,7 @@ type memStore struct { cfg StreamConfig state StreamState msgs map[uint64]*StoreMsg - fss map[string]*SimpleState + fss *stree.SubjectTree[SimpleState] dmap avl.SequenceSet maxp int64 scb StorageUpdateHandler @@ -48,7 +49,7 @@ func newMemStore(cfg *StreamConfig) (*memStore, error) { } ms := &memStore{ msgs: make(map[uint64]*StoreMsg), - fss: make(map[string]*SimpleState), + fss: stree.NewSubjectTree[SimpleState](), maxp: cfg.MaxMsgsPer, cfg: *cfg, } @@ -88,11 +89,12 @@ func (ms *memStore) UpdateConfig(cfg *StreamConfig) error { // If the value is smaller we need to enforce that. if ms.maxp != 0 && ms.maxp < maxp { lm := uint64(ms.maxp) - for subj, ss := range ms.fss { + ms.fss.Iter(func(subj []byte, ss *SimpleState) bool { if ss.Msgs > lm { - ms.enforcePerSubjectLimit(subj, ss) + ms.enforcePerSubjectLimit(bytesToString(subj), ss) } - } + return true + }) } ms.mu.Unlock() @@ -113,7 +115,8 @@ func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts int var ss *SimpleState var asl bool if len(subj) > 0 { - if ss = ms.fss[subj]; ss != nil { + var ok bool + if ss, ok = ms.fss.Find(stringToBytes(subj)); ok { asl = ms.maxp > 0 && ss.Msgs >= uint64(ms.maxp) } } @@ -191,7 +194,7 @@ func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts int ms.enforcePerSubjectLimit(subj, ss) } } else { - ms.fss[subj] = &SimpleState{Msgs: 1, First: seq, Last: seq} + ms.fss.Insert([]byte(subj), SimpleState{Msgs: 1, First: seq, Last: seq}) } } @@ -363,14 +366,17 @@ func (ms *memStore) filteredStateLocked(sseq uint64, filter string, lastPerSubje return ss } - isAll := filter == _EMPTY_ || filter == fwcs + if filter == _EMPTY_ { + filter = fwcs + } + isAll := filter == fwcs // First check if we can optimize this part. // This means we want all and the starting sequence was before this block. if isAll && sseq <= ms.state.FirstSeq { total := ms.state.Msgs if lastPerSubject { - total = uint64(len(ms.fss)) + total = uint64(ms.fss.Size()) } return SimpleState{ Msgs: total, @@ -415,21 +421,20 @@ func (ms *memStore) filteredStateLocked(sseq uint64, filter string, lastPerSubje var havePartial bool // We will track start and end sequences as we go. - for subj, fss := range ms.fss { - if isMatch(subj) { - if fss.firstNeedsUpdate { - ms.recalculateFirstForSubj(subj, fss.First, fss) - } - if sseq <= fss.First { - update(fss) - } else if sseq <= fss.Last { - // We matched but its a partial. - havePartial = true - // Don't break here, we will update to keep tracking last. - update(fss) - } + ms.fss.Match(stringToBytes(filter), func(subj []byte, fss *SimpleState) { + subjs := bytesToString(subj) + if fss.firstNeedsUpdate { + ms.recalculateFirstForSubj(subjs, fss.First, fss) } - } + if sseq <= fss.First { + update(fss) + } else if sseq <= fss.Last { + // We matched but its a partial. + havePartial = true + // Don't break here, we will update to keep tracking last. + update(fss) + } + }) // If we did not encounter any partials we can return here. if !havePartial { @@ -476,7 +481,7 @@ func (ms *memStore) filteredStateLocked(sseq uint64, filter string, lastPerSubje for seq := ms.state.FirstSeq; seq < first; seq++ { if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && isMatch(sm.subj) { if lastPerSubject { - tss = ms.fss[sm.subj] + tss, _ = ms.fss.Find(stringToBytes(sm.subj)) } // If we are last per subject, make sure to only adjust if all messages are before our first. if tss == nil || tss.Last < first { @@ -515,26 +520,29 @@ func (ms *memStore) SubjectsState(subject string) map[string]SimpleState { ms.mu.RLock() defer ms.mu.RUnlock() - if len(ms.fss) == 0 { + if ms.fss.Size() == 0 { return nil } - fss := make(map[string]SimpleState) - for subj, ss := range ms.fss { - if subject == _EMPTY_ || subject == fwcs || subjectIsSubsetMatch(subj, subject) { - if ss.firstNeedsUpdate { - ms.recalculateFirstForSubj(subj, ss.First, ss) - } - oss := fss[subj] - if oss.First == 0 { // New - fss[subj] = *ss - } else { - // Merge here. - oss.Last, oss.Msgs = ss.Last, oss.Msgs+ss.Msgs - fss[subj] = oss - } - } + if subject == _EMPTY_ { + subject = fwcs } + + fss := make(map[string]SimpleState) + ms.fss.Match(stringToBytes(subject), func(subj []byte, ss *SimpleState) { + subjs := string(subj) + if ss.firstNeedsUpdate { + ms.recalculateFirstForSubj(subjs, ss.First, ss) + } + oss := fss[subjs] + if oss.First == 0 { // New + fss[subjs] = *ss + } else { + // Merge here. + oss.Last, oss.Msgs = ss.Last, oss.Msgs+ss.Msgs + fss[subjs] = oss + } + }) return fss } @@ -543,7 +551,7 @@ func (ms *memStore) SubjectsTotals(filterSubject string) map[string]uint64 { ms.mu.RLock() defer ms.mu.RUnlock() - if len(ms.fss) == 0 { + if ms.fss.Size() == 0 { return nil } @@ -553,15 +561,16 @@ func (ms *memStore) SubjectsTotals(filterSubject string) map[string]uint64 { isAll := filterSubject == _EMPTY_ || filterSubject == fwcs fst := make(map[string]uint64) - for subj, ss := range ms.fss { + ms.fss.Match(stringToBytes(filterSubject), func(subj []byte, ss *SimpleState) { + subjs := string(subj) if isAll { - fst[subj] = ss.Msgs + fst[subjs] = ss.Msgs } else { - if tts := tokenizeSubjectIntoSlice(tsa[:0], subj); isSubsetMatchTokenized(tts, fts) { - fst[subj] = ss.Msgs + if tts := tokenizeSubjectIntoSlice(tsa[:0], subjs); isSubsetMatchTokenized(tts, fts) { + fst[subjs] = ss.Msgs } } - } + }) return fst } @@ -755,7 +764,7 @@ func (ms *memStore) purge(fseq uint64) (uint64, error) { ms.state.Bytes = 0 ms.state.Msgs = 0 ms.msgs = make(map[uint64]*StoreMsg) - ms.fss = make(map[string]*SimpleState) + ms.fss = stree.NewSubjectTree[SimpleState]() ms.mu.Unlock() if cb != nil { @@ -846,7 +855,7 @@ func (ms *memStore) reset() error { ms.state.Bytes = 0 // Reset msgs and fss. ms.msgs = make(map[uint64]*StoreMsg) - ms.fss = make(map[string]*SimpleState) + ms.fss = stree.NewSubjectTree[SimpleState]() ms.mu.Unlock() @@ -950,7 +959,8 @@ func (ms *memStore) LoadLastMsg(subject string, smp *StoreMsg) (*StoreMsg, error if subject == _EMPTY_ || subject == fwcs { sm, ok = ms.msgs[ms.state.LastSeq] } else if subjectIsLiteral(subject) { - if ss := ms.fss[subject]; ss != nil && ss.Msgs > 0 { + var ss *SimpleState + if ss, ok = ms.fss.Find(stringToBytes(subject)); ok && ss.Msgs > 0 { sm, ok = ms.msgs[ss.Last] } } else if ss := ms.filteredStateLocked(1, subject, true); ss.Msgs > 0 { @@ -982,12 +992,15 @@ func (ms *memStore) LoadNextMsg(filter string, wc bool, start uint64, smp *Store return nil, ms.state.LastSeq, ErrStoreEOF } - isAll := filter == _EMPTY_ || filter == fwcs + if filter == _EMPTY_ { + filter = fwcs + } + isAll := filter == fwcs // Skip scan of ms.fss is number of messages in the block are less than // 1/2 the number of subjects in ms.fss. Or we have a wc and lots of fss entries. const linearScanMaxFSS = 256 - doLinearScan := isAll || 2*int(ms.state.LastSeq-start) < len(ms.fss) || (wc && len(ms.fss) > linearScanMaxFSS) + doLinearScan := isAll || 2*int(ms.state.LastSeq-start) < ms.fss.Size() || (wc && ms.fss.Size() > linearScanMaxFSS) // Initial setup. fseq, lseq := start, ms.state.LastSeq @@ -996,16 +1009,14 @@ func (ms *memStore) LoadNextMsg(filter string, wc bool, start uint64, smp *Store subs := []string{filter} if wc || isAll { subs = subs[:0] - for fsubj := range ms.fss { - if isAll || subjectIsSubsetMatch(fsubj, filter) { - subs = append(subs, fsubj) - } - } + ms.fss.Match(stringToBytes(filter), func(subj []byte, val *SimpleState) { + subs = append(subs, string(subj)) + }) } fseq, lseq = ms.state.LastSeq, uint64(0) for _, subj := range subs { - ss := ms.fss[subj] - if ss == nil { + ss, ok := ms.fss.Find(stringToBytes(subj)) + if !ok { continue } if ss.firstNeedsUpdate { @@ -1093,12 +1104,12 @@ func (ms *memStore) updateFirstSeq(seq uint64) { // Remove a seq from the fss and select new first. // Lock should be held. func (ms *memStore) removeSeqPerSubject(subj string, seq uint64) { - ss := ms.fss[subj] - if ss == nil { + ss, ok := ms.fss.Find(stringToBytes(subj)) + if !ok { return } if ss.Msgs == 1 { - delete(ms.fss, subj) + ms.fss.Delete(stringToBytes(subj)) return } ss.Msgs-- @@ -1198,7 +1209,7 @@ func (ms *memStore) FastState(state *StreamState) { } } state.Consumers = ms.consumers - state.NumSubjects = len(ms.fss) + state.NumSubjects = ms.fss.Size() ms.mu.RUnlock() } @@ -1208,7 +1219,7 @@ func (ms *memStore) State() StreamState { state := ms.state state.Consumers = ms.consumers - state.NumSubjects = len(ms.fss) + state.NumSubjects = ms.fss.Size() state.Deleted = nil // Calculate interior delete details. diff --git a/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go b/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go index e82ddb293..793d57d27 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go @@ -1439,26 +1439,31 @@ func (s *Server) mqttCreateAccountSessionManager(acc *Account, quitCh chan struc } } + // Opportunistically delete the old (legacy) consumer, from v2.10.10 and + // before. Ignore any errors that might arise. + rmLegacyDurName := mqttRetainedMsgsStreamName + "_" + jsa.id + jsa.deleteConsumer(mqttRetainedMsgsStreamName, rmLegacyDurName) + // Using ephemeral consumer is too risky because if this server were to be // disconnected from the rest for few seconds, then the leader would remove // the consumer, so even after a reconnect, we would no longer receive - // retained messages. Delete any existing durable that we have for that - // and recreate here. - // The name for the durable is $MQTT_rmsgs_ (which is jsa.id) - rmDurName := mqttRetainedMsgsStreamName + "_" + jsa.id - // If error other than "not found" then fail, otherwise proceed with creating - // the durable consumer. - if _, err := jsa.deleteConsumer(mqttRetainedMsgsStreamName, rmDurName); isErrorOtherThan(err, JSConsumerNotFoundErr) { - return nil, err - } + // retained messages. + // + // So we use a durable consumer, and create a new one each time we start. + // The old one should expire and get deleted due to inactivity. The name for + // the durable is $MQTT_rmsgs_{uuid}_{server-name}, the server name is just + // for readability. + rmDurName := mqttRetainedMsgsStreamName + "_" + nuid.Next() + "_" + s.String() + ccfg := &CreateConsumerRequest{ Stream: mqttRetainedMsgsStreamName, Config: ConsumerConfig{ - Durable: rmDurName, - FilterSubject: mqttRetainedMsgsStreamSubject + ">", - DeliverSubject: rmsubj, - ReplayPolicy: ReplayInstant, - AckPolicy: AckNone, + Durable: rmDurName, + FilterSubject: mqttRetainedMsgsStreamSubject + ">", + DeliverSubject: rmsubj, + ReplayPolicy: ReplayInstant, + AckPolicy: AckNone, + InactiveThreshold: 5 * time.Minute, }, } if _, err := jsa.createConsumer(ccfg); err != nil { diff --git a/vendor/github.com/nats-io/nats-server/v2/server/opts.go b/vendor/github.com/nats-io/nats-server/v2/server/opts.go index 8925ef6df..721d6be84 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/opts.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/opts.go @@ -80,6 +80,8 @@ type ClusterOpts struct { PoolSize int `json:"-"` PinnedAccounts []string `json:"-"` Compression CompressionOpts `json:"-"` + PingInterval time.Duration `json:"-"` + MaxPingsOut int `json:"-"` // Not exported (used in tests) resolver netResolver @@ -1755,6 +1757,13 @@ func parseCluster(v interface{}, opts *Options, errors *[]error, warnings *[]err *errors = append(*errors, err) continue } + case "ping_interval": + opts.Cluster.PingInterval = parseDuration("ping_interval", tk, mv, errors, warnings) + if opts.Cluster.PingInterval > routeMaxPingInterval { + *warnings = append(*warnings, &configErr{tk, fmt.Sprintf("Cluster 'ping_interval' will reset to %v which is the max for routes", routeMaxPingInterval)}) + } + case "ping_max": + opts.Cluster.MaxPingsOut = int(mv.(int64)) default: if !tk.IsUsedVariable() { err := &unknownConfigFieldErr{ diff --git a/vendor/github.com/nats-io/nats-server/v2/server/raft.go b/vendor/github.com/nats-io/nats-server/v2/server/raft.go index 6990812c4..f041fb089 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/raft.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/raft.go @@ -154,11 +154,12 @@ type raft struct { llqrt time.Time // Last quorum lost time lsut time.Time // Last scale-up time - term uint64 // The current vote term - pterm uint64 // Previous term from the last snapshot - pindex uint64 // Previous index from the last snapshot - commit uint64 // Sequence number of the most recent commit - applied uint64 // Sequence number of the most recently applied commit + term uint64 // The current vote term + pterm uint64 // Previous term from the last snapshot + pindex uint64 // Previous index from the last snapshot + commit uint64 // Sequence number of the most recent commit + applied uint64 // Sequence number of the most recently applied commit + hcbehind bool // Were we falling behind at the last health check? (see: isCurrent) leader string // The ID of the leader vote string // Our current vote state @@ -1052,7 +1053,11 @@ func (n *raft) InstallSnapshot(data []byte) error { sn := fmt.Sprintf(snapFileT, snap.lastTerm, snap.lastIndex) sfile := filepath.Join(snapDir, sn) - if err := os.WriteFile(sfile, n.encodeSnapshot(snap), defaultFilePerms); err != nil { + <-dios + err := os.WriteFile(sfile, n.encodeSnapshot(snap), defaultFilePerms) + dios <- struct{}{} + + if err != nil { n.Unlock() // We could set write err here, but if this is a temporary situation, too many open files etc. // we want to retry and snapshots are not fatal. @@ -1187,7 +1192,11 @@ func (n *raft) loadLastSnapshot() (*snapshot, error) { if n.snapfile == _EMPTY_ { return nil, errNoSnapAvailable } + + <-dios buf, err := os.ReadFile(n.snapfile) + dios <- struct{}{} + if err != nil { n.warn("Error reading snapshot: %v", err) os.Remove(n.snapfile) @@ -1269,8 +1278,18 @@ func (n *raft) isCurrent(includeForwardProgress bool) bool { return false } + // If we were previously logging about falling behind, also log when the problem + // was cleared. + clearBehindState := func() { + if n.hcbehind { + n.warn("Health check OK, no longer falling behind") + n.hcbehind = false + } + } + // Make sure we are the leader or we know we have heard from the leader recently. if n.State() == Leader { + clearBehindState() return true } @@ -1294,6 +1313,7 @@ func (n *raft) isCurrent(includeForwardProgress bool) bool { if n.commit == n.applied { // At this point if we are current, we can return saying so. + clearBehindState() return true } else if !includeForwardProgress { // Otherwise, if we aren't allowed to include forward progress @@ -1311,11 +1331,13 @@ func (n *raft) isCurrent(includeForwardProgress bool) bool { n.Lock() if n.commit-n.applied < startDelta { // The gap is getting smaller, so we're making forward progress. + clearBehindState() return true } } } + n.hcbehind = true n.warn("Falling behind in health check, commit %d != applied %d", n.commit, n.applied) return false } @@ -3678,14 +3700,22 @@ func writePeerState(sd string, ps *peerState) error { if _, err := os.Stat(psf); err != nil && !os.IsNotExist(err) { return err } - if err := os.WriteFile(psf, encodePeerState(ps), defaultFilePerms); err != nil { + + <-dios + err := os.WriteFile(psf, encodePeerState(ps), defaultFilePerms) + dios <- struct{}{} + + if err != nil { return err } return nil } func readPeerState(sd string) (ps *peerState, err error) { + <-dios buf, err := os.ReadFile(filepath.Join(sd, peerStateFile)) + dios <- struct{}{} + if err != nil { return nil, err } @@ -3698,7 +3728,10 @@ const termVoteLen = idLen + 8 // readTermVote will read the largest term and who we voted from to stable storage. // Lock should be held. func (n *raft) readTermVote() (term uint64, voted string, err error) { + <-dios buf, err := os.ReadFile(filepath.Join(n.sd, termVoteFile)) + dios <- struct{}{} + if err != nil { return 0, noVote, err } @@ -3920,6 +3953,10 @@ func (n *raft) processVoteRequest(vr *voteRequest) error { n.resetElect(randCampaignTimeout()) } } + + // Term might have changed, make sure response has the most current + vresp.term = n.term + n.Unlock() n.sendReply(vr.reply, vresp.encode()) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/route.go b/vendor/github.com/nats-io/nats-server/v2/server/route.go index e73f1bdf7..f8a8623d6 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/route.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/route.go @@ -1780,7 +1780,15 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL, accName string) *clie // the connection as stale based on the ping interval and max out values, // but without actually sending pings. if compressionConfigured { - c.ping.tmr = time.AfterFunc(opts.PingInterval*time.Duration(opts.MaxPingsOut+1), func() { + pingInterval := opts.PingInterval + pingMax := opts.MaxPingsOut + if opts.Cluster.PingInterval > 0 { + pingInterval = opts.Cluster.PingInterval + } + if opts.Cluster.MaxPingsOut > 0 { + pingMax = opts.MaxPingsOut + } + c.ping.tmr = time.AfterFunc(pingInterval*time.Duration(pingMax+1), func() { c.mu.Lock() c.Debugf("Stale Client Connection - Closing") c.enqueueProto([]byte(fmt.Sprintf(errProto, "Stale Connection"))) diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stream.go b/vendor/github.com/nats-io/nats-server/v2/server/stream.go index f032f285b..b2bef9a86 100644 --- a/vendor/github.com/nats-io/nats-server/v2/server/stream.go +++ b/vendor/github.com/nats-io/nats-server/v2/server/stream.go @@ -637,9 +637,9 @@ func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileSt mset.store.FastState(&state) // Possible race with consumer.setLeader during recovery. - mset.mu.Lock() + mset.mu.RLock() mset.lseq = state.LastSeq - mset.mu.Unlock() + mset.mu.RUnlock() // If no msgs (new stream), set dedupe state loaded to true. if state.Msgs == 0 { @@ -5763,7 +5763,7 @@ func (a *Account) RestoreStream(ncfg *StreamConfig, r io.Reader) (*stream, error return mset, nil } -// This is to check for dangling messages on interest retention streams. +// This is to check for dangling messages on interest retention streams. Only called on account enable. // Issue https://github.com/nats-io/nats-server/issues/3612 func (mset *stream) checkForOrphanMsgs() { mset.mu.RLock() @@ -5771,9 +5771,23 @@ func (mset *stream) checkForOrphanMsgs() { for _, o := range mset.consumers { consumers = append(consumers, o) } + accName, stream := mset.acc.Name, mset.cfg.Name + + var ss StreamState + mset.store.FastState(&ss) mset.mu.RUnlock() + for _, o := range consumers { - o.checkStateForInterestStream() + if err := o.checkStateForInterestStream(); err == errAckFloorHigherThanLastSeq { + o.mu.RLock() + s, consumer := o.srv, o.name + state, _ := o.store.State() + asflr := state.AckFloor.Stream + o.mu.RUnlock() + // Warn about stream state vs our ack floor. + s.RateLimitWarnf("Detected consumer '%s > %s > %s' ack floor %d is ahead of stream's last sequence %d", + accName, stream, consumer, asflr, ss.LastSeq) + } } } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go new file mode 100644 index 000000000..79b6fd99f --- /dev/null +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go @@ -0,0 +1,68 @@ +// Copyright 2024 The NATS Authors +// 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. + +package stree + +import ( + "fmt" + "io" + "strings" +) + +// For dumping out a text representation of a tree. +func (t *SubjectTree[T]) Dump(w io.Writer) { + t.dump(w, t.root, 0) + fmt.Fprintln(w) +} + +// Will dump out a node. +func (t *SubjectTree[T]) dump(w io.Writer, n node, depth int) { + if n == nil { + fmt.Fprintf(w, "EMPTY\n") + return + } + if n.isLeaf() { + leaf := n.(*leaf[T]) + fmt.Fprintf(w, "%s LEAF: Suffix: %q Value: %+v\n", dumpPre(depth), leaf.suffix, leaf.value) + n = nil + } else { + // We are a node type here, grab meta portion. + bn := n.base() + fmt.Fprintf(w, "%s %s Prefix: %q\n", dumpPre(depth), n.kind(), bn.prefix[:bn.prefixLen]) + depth++ + n.iter(func(n node) bool { + t.dump(w, n, depth) + return true + }) + } +} + +// For individual node/leaf dumps. +func (n *leaf[T]) kind() string { return "LEAF" } +func (n *node4) kind() string { return "NODE4" } +func (n *node16) kind() string { return "NODE16" } +func (n *node256) kind() string { return "NODE256" } + +// Calculates the indendation, etc. +func dumpPre(depth int) string { + if depth == 0 { + return "-- " + } else { + var b strings.Builder + for i := 0; i < depth; i++ { + b.WriteString(" ") + } + b.WriteString("|__ ") + return b.String() + } +} diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/leaf.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/leaf.go new file mode 100644 index 000000000..839450f2e --- /dev/null +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/leaf.go @@ -0,0 +1,50 @@ +// Copyright 2023-2024 The NATS Authors +// 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. + +package stree + +import ( + "bytes" +) + +// Leaf node +type leaf[T any] struct { + // This could be the whole subject, but most likely just the suffix portion. + // We will only store the suffix here and assume all prior prefix paths have + // been checked once we arrive at this leafnode. + suffix []byte + value T +} + +func newLeaf[T any](suffix []byte, value T) *leaf[T] { + return &leaf[T]{copyBytes(suffix), value} +} + +func (n *leaf[T]) isLeaf() bool { return true } +func (n *leaf[T]) base() *meta { return nil } +func (n *leaf[T]) match(subject []byte) bool { return bytes.Equal(subject, n.suffix) } +func (n *leaf[T]) setSuffix(suffix []byte) { n.suffix = copyBytes(suffix) } +func (n *leaf[T]) isFull() bool { return true } +func (n *leaf[T]) matchParts(parts [][]byte) ([][]byte, bool) { return matchParts(parts, n.suffix) } +func (n *leaf[T]) iter(f func(node) bool) {} +func (n *leaf[T]) children() []node { return nil } +func (n *leaf[T]) numChildren() uint16 { return 0 } +func (n *leaf[T]) path() []byte { return n.suffix } + +// Not applicable to leafs and should not be called, so panic if we do. +func (n *leaf[T]) setPrefix(pre []byte) { panic("setPrefix called on leaf") } +func (n *leaf[T]) addChild(_ byte, _ node) { panic("addChild called on leaf") } +func (n *leaf[T]) findChild(_ byte) *node { panic("findChild called on leaf") } +func (n *leaf[T]) grow() node { panic("grow called on leaf") } +func (n *leaf[T]) deleteChild(_ byte) { panic("deleteChild called on leaf") } +func (n *leaf[T]) shrink() node { panic("shrink called on leaf") } diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go new file mode 100644 index 000000000..0afecfab9 --- /dev/null +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go @@ -0,0 +1,45 @@ +// Copyright 2023-2024 The NATS Authors +// 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. + +package stree + +// Internal node interface. +type node interface { + isLeaf() bool + base() *meta + setPrefix(pre []byte) + addChild(c byte, n node) + findChild(c byte) *node + deleteChild(c byte) + isFull() bool + grow() node + shrink() node + matchParts(parts [][]byte) ([][]byte, bool) + kind() string + iter(f func(node) bool) + children() []node + numChildren() uint16 + path() []byte +} + +// Maximum prefix len +// We expect the most savings to come from long shared prefixes. +// This also makes the meta base layer exactly 64 bytes, a normal L1 cache line. +const maxPrefixLen = 60 + +// 64 bytes total - an L1 cache line. +type meta struct { + prefix [maxPrefixLen]byte + prefixLen uint16 + size uint16 +} diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go new file mode 100644 index 000000000..b61f286dc --- /dev/null +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go @@ -0,0 +1,121 @@ +// Copyright 2023-2024 The NATS Authors +// 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. + +package stree + +// Node with 16 children +type node16 struct { + meta + child [16]node + key [16]byte +} + +func newNode16(prefix []byte) *node16 { + nn := &node16{} + nn.setPrefix(prefix) + return nn +} + +func (n *node16) isLeaf() bool { return false } +func (n *node16) base() *meta { return &n.meta } + +func (n *node16) setPrefix(pre []byte) { + n.prefixLen = uint16(min(len(pre), maxPrefixLen)) + for i := uint16(0); i < n.prefixLen; i++ { + n.prefix[i] = pre[i] + } +} + +// Currently we do not keep node16 sorted or use bitfields for traversal so just add to the end. +// TODO(dlc) - We should revisit here with more detailed benchmarks. +func (n *node16) addChild(c byte, nn node) { + if n.size >= 16 { + panic("node16 full!") + } + n.key[n.size] = c + n.child[n.size] = nn + n.size++ +} + +func (n *node16) numChildren() uint16 { return n.size } +func (n *node16) path() []byte { return n.prefix[:n.prefixLen] } + +func (n *node16) findChild(c byte) *node { + for i := uint16(0); i < n.size; i++ { + if n.key[i] == c { + return &n.child[i] + } + } + return nil +} + +func (n *node16) isFull() bool { return n.size >= 16 } + +func (n *node16) grow() node { + nn := newNode256(n.prefix[:n.prefixLen]) + for i := 0; i < 16; i++ { + nn.addChild(n.key[i], n.child[i]) + } + return nn +} + +// Deletes a child from the node. +func (n *node16) deleteChild(c byte) { + for i, last := uint16(0), n.size-1; i < n.size; i++ { + if n.key[i] == c { + // Unsorted so just swap in last one here, else nil if last. + if i < last { + n.key[i] = n.key[last] + n.child[i] = n.child[last] + n.key[last] = 0 + n.child[last] = nil + } else { + n.key[i] = 0 + n.child[i] = nil + } + n.size-- + return + } + } +} + +// Shrink if needed and return new node, otherwise return nil. +func (n *node16) shrink() node { + if n.size > 4 { + return nil + } + nn := newNode4(nil) + for i := uint16(0); i < n.size; i++ { + nn.addChild(n.key[i], n.child[i]) + } + return nn +} + +// Will match parts against our prefix.no +func (n *node16) matchParts(parts [][]byte) ([][]byte, bool) { + return matchParts(parts, n.prefix[:n.prefixLen]) +} + +// Iterate over all children calling func f. +func (n *node16) iter(f func(node) bool) { + for i := uint16(0); i < n.size; i++ { + if !f(n.child[i]) { + return + } + } +} + +// Return our children as a slice. +func (n *node16) children() []node { + return n.child[:n.size] +} diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go new file mode 100644 index 000000000..c7bcd3362 --- /dev/null +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go @@ -0,0 +1,97 @@ +// Copyright 2023-2024 The NATS Authors +// 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. + +package stree + +// Node with 256 children +type node256 struct { + meta + child [256]node +} + +func newNode256(prefix []byte) *node256 { + nn := &node256{} + nn.setPrefix(prefix) + return nn +} + +func (n *node256) isLeaf() bool { return false } +func (n *node256) base() *meta { return &n.meta } + +func (n *node256) setPrefix(pre []byte) { + n.prefixLen = uint16(min(len(pre), maxPrefixLen)) + for i := uint16(0); i < n.prefixLen; i++ { + n.prefix[i] = pre[i] + } +} + +func (n *node256) addChild(c byte, nn node) { + n.child[c] = nn + n.size++ +} + +func (n *node256) numChildren() uint16 { return n.size } +func (n *node256) path() []byte { return n.prefix[:n.prefixLen] } + +func (n *node256) findChild(c byte) *node { + if n.child[c] != nil { + return &n.child[c] + } + return nil +} + +func (n *node256) isFull() bool { return false } +func (n *node256) grow() node { panic("grow can not be called on node256") } + +// Deletes a child from the node. +func (n *node256) deleteChild(c byte) { + if n.child[c] != nil { + n.child[c] = nil + n.size-- + } +} + +// Shrink if needed and return new node, otherwise return nil. +func (n *node256) shrink() node { + if n.size > 16 { + return nil + } + nn := newNode16(nil) + for c, child := range n.child { + if child != nil { + nn.addChild(byte(c), n.child[c]) + } + } + return nn +} + +// Will match parts against our prefix. +func (n *node256) matchParts(parts [][]byte) ([][]byte, bool) { + return matchParts(parts, n.prefix[:n.prefixLen]) +} + +// Iterate over all children calling func f. +func (n *node256) iter(f func(node) bool) { + for i := 0; i < 256; i++ { + if n.child[i] != nil { + if !f(n.child[i]) { + return + } + } + } +} + +// Return our children as a slice. +func (n *node256) children() []node { + return n.child[:256] +} diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go new file mode 100644 index 000000000..195299e26 --- /dev/null +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go @@ -0,0 +1,116 @@ +// Copyright 2023-2024 The NATS Authors +// 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. + +package stree + +// Node with 4 children +type node4 struct { + meta + child [4]node + key [4]byte +} + +func newNode4(prefix []byte) *node4 { + nn := &node4{} + nn.setPrefix(prefix) + return nn +} + +func (n *node4) isLeaf() bool { return false } +func (n *node4) base() *meta { return &n.meta } + +func (n *node4) setPrefix(pre []byte) { + n.prefixLen = uint16(min(len(pre), maxPrefixLen)) + for i := uint16(0); i < n.prefixLen; i++ { + n.prefix[i] = pre[i] + } +} + +// Currently we do not need to keep sorted for traversal so just add to the end. +func (n *node4) addChild(c byte, nn node) { + if n.size >= 4 { + panic("node4 full!") + } + n.key[n.size] = c + n.child[n.size] = nn + n.size++ +} + +func (n *node4) numChildren() uint16 { return n.size } +func (n *node4) path() []byte { return n.prefix[:n.prefixLen] } + +func (n *node4) findChild(c byte) *node { + for i := uint16(0); i < n.size; i++ { + if n.key[i] == c { + return &n.child[i] + } + } + return nil +} + +func (n *node4) isFull() bool { return n.size >= 4 } + +func (n *node4) grow() node { + nn := newNode16(n.prefix[:n.prefixLen]) + for i := 0; i < 4; i++ { + nn.addChild(n.key[i], n.child[i]) + } + return nn +} + +// Deletes a child from the node. +func (n *node4) deleteChild(c byte) { + for i, last := uint16(0), n.size-1; i < n.size; i++ { + if n.key[i] == c { + // Unsorted so just swap in last one here, else nil if last. + if i < last { + n.key[i] = n.key[last] + n.child[i] = n.child[last] + n.key[last] = 0 + n.child[last] = nil + } else { + n.key[i] = 0 + n.child[i] = nil + } + n.size-- + return + } + } +} + +// Shrink if needed and return new node, otherwise return nil. +func (n *node4) shrink() node { + if n.size == 1 { + return n.child[0] + } + return nil +} + +// Will match parts against our prefix. +func (n *node4) matchParts(parts [][]byte) ([][]byte, bool) { + return matchParts(parts, n.prefix[:n.prefixLen]) +} + +// Iterate over all children calling func f. +func (n *node4) iter(f func(node) bool) { + for i := uint16(0); i < n.size; i++ { + if !f(n.child[i]) { + return + } + } +} + +// Return our children as a slice. +func (n *node4) children() []node { + return n.child[:n.size] +} diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go new file mode 100644 index 000000000..71783ee11 --- /dev/null +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go @@ -0,0 +1,134 @@ +// Copyright 2023-2024 The NATS Authors +// 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. + +package stree + +import ( + "bytes" +) + +// genParts will break a filter subject up into parts. +// We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'. +// We do not care about other tokens per se, just parts that are separated by wildcards with an optional end fwc. +func genParts(filter []byte, parts [][]byte) [][]byte { + var start int + for i, e := 0, len(filter)-1; i < len(filter); i++ { + if filter[i] == tsep { + // See if next token is pwc. Either internal or end pwc. + if i < e && filter[i+1] == pwc && (i+2 <= e && filter[i+2] == tsep || i+1 == e) { + if i > start { + parts = append(parts, filter[start:i+1]) + } + parts = append(parts, filter[i+1:i+2]) + i++ // Skip pwc + if i+2 <= e { + i++ // Skip next tsep from next part too. + } + start = i + 1 + } else if i < e && filter[i+1] == fwc && i+1 == e { + // We have a fwc + if i > start { + parts = append(parts, filter[start:i+1]) + } + parts = append(parts, filter[i+1:i+2]) + i++ // Skip fwc + start = i + 1 + } + } else if filter[i] == pwc || filter[i] == fwc { + // We start with a pwc or fwc. + parts = append(parts, filter[i:i+1]) + if i+1 <= e { + i++ // Skip next tsep from next part too. + } + start = i + 1 + } + } + if start < len(filter) { + // Check to see if we need to eat a leading tsep. + if filter[start] == tsep { + start++ + } + parts = append(parts, filter[start:]) + } + return parts +} + +// Match our parts against a fragment, which could be prefix for nodes or a suffix for leafs. +func matchParts(parts [][]byte, frag []byte) ([][]byte, bool) { + if len(frag) == 0 { + return parts, true + } + + var si int + lpi := len(parts) - 1 + lf := len(frag) + + for i, part := range parts { + if si >= lf { + return parts[i:], true + } + lp := len(part) + // Check for pwc or fwc place holders. + if lp == 1 { + if part[0] == pwc { + index := bytes.IndexByte(frag[si:], tsep) + // We are trying to match pwc and did not find our tsep. + // Will need to move to next node from caller. + if index < 0 { + if i == lpi { + return nil, true + } + return parts[i:], true + } + si += index + 1 + continue + } else if part[0] == fwc { + // If we are here we should be good. + return nil, true + } + } + end := min(si+lp, len(frag)) + // If part is bigger then the fragment, adjust to a portion on the part. + partialPart := lp > end + if partialPart { + // Frag is smaller then part itself. + part = part[:end] + } + if !bytes.Equal(part, frag[si:end]) { + return parts, false + } + // If we still have a portion of the fragment left, update and continue. + if end < lf { + si = end + continue + } + // If we matched a partial, do not move past current part + // but update the part to what was consumed. This allows upper layers to continue. + if end < lp { + if end >= lf { + parts = append([][]byte{}, parts...) // Create a copy before modifying. + parts[i] = parts[i][lf:] + } else { + i++ + } + return parts[i:], true + } + if i == lpi { + return nil, true + } + // If we are here we are not the last part which means we have a wildcard + // gap, so we need to match anything up to next tsep. + si += len(part) + } + return parts, false +} diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go new file mode 100644 index 000000000..452f5f35e --- /dev/null +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go @@ -0,0 +1,380 @@ +// Copyright 2023-2024 The NATS Authors +// 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. + +package stree + +import ( + "bytes" + "sort" +) + +// SubjectTree is an adaptive radix trie (ART) for storing subject information on literal subjects. +// Will use dynamic nodes, path compression and lazy expansion. +// The reason this exists is to not only save some memory in our filestore but to greatly optimize matching +// a wildcard subject to certain members, e.g. consumer NumPending calculations. +type SubjectTree[T any] struct { + root node + size int +} + +// NewSubjectTree creates a new SubjectTree with values T. +func NewSubjectTree[T any]() *SubjectTree[T] { + return &SubjectTree[T]{} +} + +// Size returns the number of elements stored. +func (t *SubjectTree[T]) Size() int { + if t == nil { + return 0 + } + return t.size +} + +// Will empty out the tree, or if tree is nil create a new one. +func (t *SubjectTree[T]) Empty() *SubjectTree[T] { + if t == nil { + return NewSubjectTree[T]() + } + t.root, t.size = nil, 0 + return t +} + +// Insert a value into the tree. Will return if the value was updated and if so the old value. +func (t *SubjectTree[T]) Insert(subject []byte, value T) (*T, bool) { + old, updated := t.insert(&t.root, subject, value, 0) + if !updated { + t.size++ + } + return old, updated +} + +// Find will find the value and return it or false if it was not found. +func (t *SubjectTree[T]) Find(subject []byte) (*T, bool) { + var si uint16 + for n := t.root; n != nil; { + if n.isLeaf() { + if ln := n.(*leaf[T]); ln.match(subject[si:]) { + return &ln.value, true + } + return nil, false + } + // We are a node type here, grab meta portion. + if bn := n.base(); bn.prefixLen > 0 { + end := min(int(si+bn.prefixLen), len(subject)) + if !bytes.Equal(subject[si:end], bn.prefix[:bn.prefixLen]) { + return nil, false + } + // Increment our subject index. + si += bn.prefixLen + } + if an := n.findChild(pivot(subject, si)); an != nil { + n = *an + } else { + return nil, false + } + } + return nil, false +} + +// Delete will delete the item and return its value, or not found if it did not exist. +func (t *SubjectTree[T]) Delete(subject []byte) (*T, bool) { + val, deleted := t.delete(&t.root, subject, 0) + if deleted { + t.size-- + } + return val, deleted +} + +// Match will match against a subject that can have wildcards and invoke the callback func for each matched value. +func (t *SubjectTree[T]) Match(filter []byte, cb func(subject []byte, val *T)) { + if len(filter) == 0 || cb == nil { + return + } + // We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'. + var raw [16][]byte + parts := genParts(filter, raw[:0]) + var _pre [256]byte + t.match(t.root, parts, _pre[:0], cb) +} + +// Iter will walk all entries in the SubjectTree lexographically. The callback can return false to terminate the walk. +func (t *SubjectTree[T]) Iter(cb func(subject []byte, val *T) bool) { + if t == nil || t.root == nil { + return + } + var _pre [256]byte + t.iter(t.root, _pre[:0], cb) +} + +// Internal methods + +// Internal call to insert that can be recursive. +func (t *SubjectTree[T]) insert(np *node, subject []byte, value T, si int) (*T, bool) { + n := *np + if n == nil { + *np = newLeaf(subject, value) + return nil, false + } + if n.isLeaf() { + ln := n.(*leaf[T]) + if ln.match(subject[si:]) { + // Replace with new value. + old := ln.value + ln.value = value + return &old, true + } + // Here we need to split this leaf. + cpi := commonPrefixLen(ln.suffix, subject[si:]) + nn := newNode4(subject[si : si+cpi]) + ln.setSuffix(ln.suffix[cpi:]) + si += cpi + // Make sure we have different pivot, normally this will be the case unless we have overflowing prefixes. + if p := pivot(ln.suffix, 0); si < len(subject) && p == subject[si] { + // We need to split the original leaf. Recursively call into insert. + t.insert(np, subject, value, si) + // Now add the update version of *np as a child to the new node4. + nn.addChild(p, *np) + } else { + // Can just add this new leaf as a sibling. + nl := newLeaf(subject[si:], value) + nn.addChild(pivot(nl.suffix, 0), nl) + // Add back original. + nn.addChild(pivot(ln.suffix, 0), ln) + } + *np = nn + return nil, false + } + + // Non-leaf nodes. + bn := n.base() + if bn.prefixLen > 0 { + cpi := commonPrefixLen(bn.prefix[:bn.prefixLen], subject[si:]) + if pli := int(bn.prefixLen); cpi >= pli { + // Move past this node. We look for an existing child node to recurse into. + // If one does not exist we can create a new leaf node. + si += pli + if nn := n.findChild(pivot(subject, si)); nn != nil { + return t.insert(nn, subject, value, si) + } + if n.isFull() { + n = n.grow() + *np = n + } + n.addChild(pivot(subject, si), newLeaf(subject[si:], value)) + return nil, false + } else { + // We did not match the prefix completely here. + // Calculate new prefix for this node. + prefix := subject[si : si+cpi] + si += len(prefix) + // We will insert a new node4 and attach our current node below after adjusting prefix. + nn := newNode4(prefix) + // Shift the prefix for our original node. + n.setPrefix(bn.prefix[cpi:bn.prefixLen]) + nn.addChild(pivot(bn.prefix[:], 0), n) + // Add in our new leaf. + nn.addChild(pivot(subject[si:], 0), newLeaf(subject[si:], value)) + // Update our node reference. + *np = nn + } + } else { + if nn := n.findChild(pivot(subject, si)); nn != nil { + return t.insert(nn, subject, value, si) + } + // No prefix and no matched child, so add in new leafnode as needed. + if n.isFull() { + n = n.grow() + *np = n + } + n.addChild(pivot(subject, si), newLeaf(subject[si:], value)) + } + + return nil, false +} + +// internal function to recursively find the leaf to delete. Will do compaction if the item is found and removed. +func (t *SubjectTree[T]) delete(np *node, subject []byte, si uint16) (*T, bool) { + if t == nil || np == nil || *np == nil || len(subject) == 0 { + return nil, false + } + n := *np + if n.isLeaf() { + ln := n.(*leaf[T]) + if ln.match(subject[si:]) { + *np = nil + return &ln.value, true + } + return nil, false + } + // Not a leaf node. + if bn := n.base(); bn.prefixLen > 0 { + if !bytes.Equal(subject[si:si+bn.prefixLen], bn.prefix[:bn.prefixLen]) { + return nil, false + } + // Increment our subject index. + si += bn.prefixLen + } + p := pivot(subject, si) + nna := n.findChild(p) + if nna == nil { + return nil, false + } + nn := *nna + if nn.isLeaf() { + ln := nn.(*leaf[T]) + if ln.match(subject[si:]) { + n.deleteChild(p) + + if sn := n.shrink(); sn != nil { + bn := n.base() + // Make sure to set cap so we force an append to copy below. + pre := bn.prefix[:bn.prefixLen:bn.prefixLen] + // Need to fix up prefixes/suffixes. + if sn.isLeaf() { + ln := sn.(*leaf[T]) + // Make sure to set cap so we force an append to copy. + ln.suffix = append(pre, ln.suffix...) + } else { + // We are a node here, we need to add in the old prefix. + if len(pre) > 0 { + bsn := sn.base() + sn.setPrefix(append(pre, bsn.prefix[:bsn.prefixLen]...)) + } + } + *np = sn + } + + return &ln.value, true + } + return nil, false + } + return t.delete(nna, subject, si) +} + +// Internal function which can be called recursively to match all leaf nodes to a given filter subject which +// once here has been decomposed to parts. These parts only care about wildcards, both pwc and fwc. +func (t *SubjectTree[T]) match(n node, parts [][]byte, pre []byte, cb func(subject []byte, val *T)) { + // Capture if we are sitting on a terminal fwc. + var hasFWC bool + if lp := len(parts); lp > 0 && parts[lp-1][0] == fwc { + hasFWC = true + } + + for n != nil { + nparts, matched := n.matchParts(parts) + // Check if we did not match. + if !matched { + return + } + // We have matched here. If we are a leaf and have exhausted all parts or he have a FWC fire callback. + if n.isLeaf() { + if len(nparts) == 0 || hasFWC { + ln := n.(*leaf[T]) + cb(append(pre, ln.suffix...), &ln.value) + } + return + } + // We have normal nodes here. + // We need to append our prefix + bn := n.base() + if bn.prefixLen > 0 { + // Note that this append may reallocate, but it doesn't modify "pre" at the "match" callsite. + pre = append(pre, bn.prefix[:bn.prefixLen]...) + } + + // Check our remaining parts. + if len(nparts) == 0 && !hasFWC { + // We are a node with no parts left and we are not looking at a fwc. + // We could have a leafnode with no suffix which would be a match. + // We could also have a terminal pwc. Check for those here. + var hasTermPWC bool + if lp := len(parts); lp > 0 && len(parts[lp-1]) == 1 && parts[lp-1][0] == pwc { + // If we are sitting on a terminal pwc, put the pwc back and continue. + nparts = parts[len(parts)-1:] + hasTermPWC = true + } + for _, cn := range n.children() { + if cn == nil { + continue + } + if cn.isLeaf() { + ln := cn.(*leaf[T]) + if len(ln.suffix) == 0 { + cb(append(pre, ln.suffix...), &ln.value) + } else if hasTermPWC && bytes.IndexByte(ln.suffix, tsep) < 0 { + cb(append(pre, ln.suffix...), &ln.value) + } + } else if hasTermPWC { + // We have terminal pwc so call into match again with the child node. + t.match(cn, nparts, pre, cb) + } + } + // Return regardless. + return + } + // If we are sitting on a terminal fwc, put back and continue. + if hasFWC && len(nparts) == 0 { + nparts = parts[len(parts)-1:] + } + + // Here we are a node type with a partial match. + // Check if the first part is a wildcard. + fp := nparts[0] + p := pivot(fp, 0) + // Check if we have a pwc/fwc part here. This will cause us to iterate. + if len(fp) == 1 && (p == pwc || p == fwc) { + // We need to iterate over all children here for the current node + // to see if we match further down. + for _, cn := range n.children() { + if cn != nil { + t.match(cn, nparts, pre, cb) + } + } + } + // Here we have normal traversal, so find the next child. + nn := n.findChild(p) + if nn == nil { + return + } + n, parts = *nn, nparts + } +} + +// Interal iter function to walk nodes in lexigraphical order. +func (t *SubjectTree[T]) iter(n node, pre []byte, cb func(subject []byte, val *T) bool) bool { + if n.isLeaf() { + ln := n.(*leaf[T]) + return cb(append(pre, ln.suffix...), &ln.value) + } + // We are normal node here. + bn := n.base() + // Note that this append may reallocate, but it doesn't modify "pre" at the "iter" callsite. + pre = append(pre, bn.prefix[:bn.prefixLen]...) + // Collect nodes since unsorted. + var _nodes [256]node + nodes := _nodes[:0] + for _, cn := range n.children() { + if cn != nil { + nodes = append(nodes, cn) + } + } + // Now sort. + sort.SliceStable(nodes, func(i, j int) bool { return bytes.Compare(nodes[i].path(), nodes[j].path()) < 0 }) + // Now walk the nodes in order and call into next iter. + for i := range nodes { + if !t.iter(nodes[i], pre, cb) { + return false + } + } + return true +} diff --git a/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go b/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go new file mode 100644 index 000000000..5ced54bc9 --- /dev/null +++ b/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go @@ -0,0 +1,61 @@ +// Copyright 2023-2024 The NATS Authors +// 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. + +package stree + +// For subject matching. +const ( + pwc = '*' + fwc = '>' + tsep = '.' +) + +// Determine index of common prefix. No match at all is 0, etc. +func commonPrefixLen(s1, s2 []byte) int { + limit := min(len(s1), len(s2)) + var i int + for ; i < limit; i++ { + if s1[i] != s2[i] { + break + } + } + return min(i, maxPrefixLen) +} + +// Helper to copy bytes. +func copyBytes(src []byte) []byte { + if len(src) == 0 { + return nil + } + dst := make([]byte, len(src)) + copy(dst, src) + return dst +} + +type position interface{ int | uint16 } + +// Can return 0 if we have all the subject as prefixes. +func pivot[N position](subject []byte, pos N) byte { + if int(pos) >= len(subject) { + return 0 + } + return subject[pos] +} + +// TODO(dlc) - Can be removed with Go 1.21 once server is on Go 1.22. +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/vendor/modules.txt b/vendor/modules.txt index d1db0b36c..a616ffaad 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1241,9 +1241,10 @@ github.com/justinas/alice # github.com/kevinburke/ssh_config v1.2.0 ## explicit github.com/kevinburke/ssh_config -# github.com/klauspost/compress v1.17.4 +# github.com/klauspost/compress v1.17.5 ## explicit; go 1.19 github.com/klauspost/compress/flate +github.com/klauspost/compress/internal/race github.com/klauspost/compress/s2 # github.com/klauspost/cpuid/v2 v2.1.0 ## explicit; go 1.15 @@ -1398,7 +1399,7 @@ github.com/mschoch/smat # github.com/nats-io/jwt/v2 v2.5.3 ## explicit; go 1.18 github.com/nats-io/jwt/v2 -# github.com/nats-io/nats-server/v2 v2.10.9 +# github.com/nats-io/nats-server/v2 v2.10.10 ## explicit; go 1.20 github.com/nats-io/nats-server/v2/conf github.com/nats-io/nats-server/v2/internal/fastrand @@ -1409,6 +1410,7 @@ github.com/nats-io/nats-server/v2/server/avl github.com/nats-io/nats-server/v2/server/certidp github.com/nats-io/nats-server/v2/server/certstore github.com/nats-io/nats-server/v2/server/pse +github.com/nats-io/nats-server/v2/server/stree github.com/nats-io/nats-server/v2/server/sysmem # github.com/nats-io/nats.go v1.32.0 ## explicit; go 1.20 From ea500bfab121dbcf4a334177818bbc21e6a86a59 Mon Sep 17 00:00:00 2001 From: kobergj Date: Wed, 7 Feb 2024 10:12:53 +0000 Subject: [PATCH 041/112] Automated changelog update [skip ci] --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c93ac78bd..68f6535c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ The following sections list the changes for unreleased. * Enhancement - Allow restarting multiple uploads with one command: [#8287](https://github.com/owncloud/ocis/pull/8287) * Enhancement - Modify the concurrency default: [#8309](https://github.com/owncloud/ocis/pull/8309) * Enhancement - Bump reva: [#8340](https://github.com/owncloud/ocis/pull/8340) +* Enhancement - Allow sending multiple user ids in one sse event: [#8379](https://github.com/owncloud/ocis/pull/8379) ## Details @@ -322,6 +323,14 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/issues/8291 https://github.com/owncloud/ocis/pull/8340 +* Enhancement - Allow sending multiple user ids in one sse event: [#8379](https://github.com/owncloud/ocis/pull/8379) + + Sending multiple user ids in one sse event is now possible which reduces the + number of sent events. + + https://github.com/owncloud/ocis/pull/8379 + https://github.com/cs3org/reva/pull/4501 + # Changelog for [5.0.0-rc.1] (2023-12-27) The following sections list the changes for 5.0.0-rc.1. From 46dc9100ef7d953ff22794531a5b6fdb2e9232d7 Mon Sep 17 00:00:00 2001 From: mmattel Date: Wed, 7 Feb 2024 11:22:10 +0100 Subject: [PATCH 042/112] rename files --- docs/env-var-deltas/{4.0.5-5.0.0.adoc => 4.0.5-5.0.0-added.adoc} | 0 docs/env-var-deltas/{4.0.5-5.0.0.md => 4.0.5-5.0.0-added.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename docs/env-var-deltas/{4.0.5-5.0.0.adoc => 4.0.5-5.0.0-added.adoc} (100%) rename docs/env-var-deltas/{4.0.5-5.0.0.md => 4.0.5-5.0.0-added.md} (100%) diff --git a/docs/env-var-deltas/4.0.5-5.0.0.adoc b/docs/env-var-deltas/4.0.5-5.0.0-added.adoc similarity index 100% rename from docs/env-var-deltas/4.0.5-5.0.0.adoc rename to docs/env-var-deltas/4.0.5-5.0.0-added.adoc diff --git a/docs/env-var-deltas/4.0.5-5.0.0.md b/docs/env-var-deltas/4.0.5-5.0.0-added.md similarity index 100% rename from docs/env-var-deltas/4.0.5-5.0.0.md rename to docs/env-var-deltas/4.0.5-5.0.0-added.md From 78be3b2256ca38e903d51604f43ec490554edaf2 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 7 Feb 2024 11:41:50 +0100 Subject: [PATCH 043/112] Add table for removed variables between 4.0.0 and 5.0.0 Co-authored-by: Martin Mattel Signed-off-by: Christian Richter --- .../{4.0.5-5.0.0-added.adoc => 4.0.0-5.0.0-added.adoc} | 0 .../{4.0.5-5.0.0-added.md => 4.0.0-5.0.0-added.md} | 0 docs/env-var-deltas/4.0.0-5.0.0-removed.md | 10 ++++++++++ 3 files changed, 10 insertions(+) rename docs/env-var-deltas/{4.0.5-5.0.0-added.adoc => 4.0.0-5.0.0-added.adoc} (100%) rename docs/env-var-deltas/{4.0.5-5.0.0-added.md => 4.0.0-5.0.0-added.md} (100%) create mode 100644 docs/env-var-deltas/4.0.0-5.0.0-removed.md diff --git a/docs/env-var-deltas/4.0.5-5.0.0-added.adoc b/docs/env-var-deltas/4.0.0-5.0.0-added.adoc similarity index 100% rename from docs/env-var-deltas/4.0.5-5.0.0-added.adoc rename to docs/env-var-deltas/4.0.0-5.0.0-added.adoc diff --git a/docs/env-var-deltas/4.0.5-5.0.0-added.md b/docs/env-var-deltas/4.0.0-5.0.0-added.md similarity index 100% rename from docs/env-var-deltas/4.0.5-5.0.0-added.md rename to docs/env-var-deltas/4.0.0-5.0.0-added.md diff --git a/docs/env-var-deltas/4.0.0-5.0.0-removed.md b/docs/env-var-deltas/4.0.0-5.0.0-removed.md new file mode 100644 index 000000000..a571c8e00 --- /dev/null +++ b/docs/env-var-deltas/4.0.0-5.0.0-removed.md @@ -0,0 +1,10 @@ +# Removed Variables between oCIS 4.0.5 and oCIS 5.0.0-rc1 + +| File | Variable | Replacement | Description | +|------------------------------------------|--------------------------------------|-------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| services/auth-basic/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | +| services/graph/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | +| services/groups/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | +| services/idp/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | +| services/users/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | +| services/users/pkg/config/config.go | `LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` | `OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` | Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's. | From 44a044f5dbc66d5137719e3b0e1c0472164e89de Mon Sep 17 00:00:00 2001 From: mmattel Date: Wed, 7 Feb 2024 12:05:01 +0100 Subject: [PATCH 044/112] convert markdown to adoc Signed-off-by: mmattel --- docs/env-var-deltas/4.0.0-5.0.0-added.adoc | 2 +- docs/env-var-deltas/4.0.0-5.0.0-added.md | 2 +- docs/env-var-deltas/4.0.0-5.0.0-removed.adoc | 40 +++++++++++++++++++ docs/env-var-deltas/4.0.0-5.0.0-removed.md | 2 +- .../make-adoc-table-from-md.txt | 38 +++++++++--------- 5 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 docs/env-var-deltas/4.0.0-5.0.0-removed.adoc diff --git a/docs/env-var-deltas/4.0.0-5.0.0-added.adoc b/docs/env-var-deltas/4.0.0-5.0.0-added.adoc index d4a4a066b..7498bf465 100644 --- a/docs/env-var-deltas/4.0.0-5.0.0-added.adoc +++ b/docs/env-var-deltas/4.0.0-5.0.0-added.adoc @@ -1,4 +1,4 @@ -// # Added Variables between oCIS 4.0.5 and oCIS 5.0.0-rc +// # Added Variables between oCIS 4.0.0 and oCIS 5.0.0 // commenting the headline to make it better includable // table created based on the .md file per 2024.02.06 diff --git a/docs/env-var-deltas/4.0.0-5.0.0-added.md b/docs/env-var-deltas/4.0.0-5.0.0-added.md index 41bcf5b87..f2105f1f7 100644 --- a/docs/env-var-deltas/4.0.0-5.0.0-added.md +++ b/docs/env-var-deltas/4.0.0-5.0.0-added.md @@ -1,4 +1,4 @@ -# Added Variables between oCIS 4.0.5 and oCIS 5.0.0-rc1 +# Added Variables between oCIS 4.0.0 and oCIS 5.0.0 | File | Variable | Description | Default | |-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------| diff --git a/docs/env-var-deltas/4.0.0-5.0.0-removed.adoc b/docs/env-var-deltas/4.0.0-5.0.0-removed.adoc new file mode 100644 index 000000000..38225205a --- /dev/null +++ b/docs/env-var-deltas/4.0.0-5.0.0-removed.adoc @@ -0,0 +1,40 @@ +// Removed Variables between oCIS 4.0.0 and oCIS 5.0.0 +// commenting the headline to make it better includable + +// table created based on the .md file per 2024.02.07 +// the table should be recreated/updated based on the final .md file + +[width="100%",cols="~,~,~,~",options="header"] +|=== +| File| Variable| Replacement| Description + +| xref:{s-path}/auth-basic.adoc[auth-basic] +| `LDAP_BIND_PASSWORD` +| `OCIS_LDAP_BIND_PASSWORD` +| Password to use for authenticating the 'bind_dn'. + +| xref:{s-path}/graph.adoc[graph] +| `LDAP_BIND_PASSWORD` +| `OCIS_LDAP_BIND_PASSWORD` +| Password to use for authenticating the 'bind_dn'. + +| xref:{s-path}/groups.adoc[groups] +| `LDAP_BIND_PASSWORD` +| `OCIS_LDAP_BIND_PASSWORD` +| Password to use for authenticating the 'bind_dn'. + +| xref:{s-path}/idp.adoc[idp] +| `LDAP_BIND_PASSWORD` +| `OCIS_LDAP_BIND_PASSWORD` +| Password to use for authenticating the 'bind_dn'. + +| xref:{s-path}/users.adoc[users] +| `LDAP_BIND_PASSWORD` +| `OCIS_LDAP_BIND_PASSWORD` +| Password to use for authenticating the 'bind_dn'. + +| +| `LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` +| `OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` +| Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's. +|=== diff --git a/docs/env-var-deltas/4.0.0-5.0.0-removed.md b/docs/env-var-deltas/4.0.0-5.0.0-removed.md index a571c8e00..2869d71e8 100644 --- a/docs/env-var-deltas/4.0.0-5.0.0-removed.md +++ b/docs/env-var-deltas/4.0.0-5.0.0-removed.md @@ -1,4 +1,4 @@ -# Removed Variables between oCIS 4.0.5 and oCIS 5.0.0-rc1 +# Removed Variables between oCIS 4.0.0 and oCIS 5.0.0 | File | Variable | Replacement | Description | |------------------------------------------|--------------------------------------|-------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| diff --git a/docs/env-var-deltas/make-adoc-table-from-md.txt b/docs/env-var-deltas/make-adoc-table-from-md.txt index 47ffef568..f2f256ea6 100644 --- a/docs/env-var-deltas/make-adoc-table-from-md.txt +++ b/docs/env-var-deltas/make-adoc-table-from-md.txt @@ -1,8 +1,8 @@ make the markdown a asciidoc table -cp 4.0.5-5.0.0.md 4.0.5-5.0.0.adoc copy file -sed -i 's/.$//g' 4.0.5-5.0.0.adoc remove last character of each line which is | -sed -i 's/ \+//g' 4.0.5-5.0.0.adoc remove any double spaces +cp 4.0.0-5.0.0.md 4.0.0-5.0.0.adoc copy file +sed -i 's/.$//g' 4.0.0-5.0.0.adoc remove last character of each line which is | +sed -i 's/ \+//g' 4.0.0-5.0.0.adoc remove any double spaces fully remove the line containing |----...| rename: | File| Variable| Description| Default --> | Service| Variable| Description| Default @@ -18,28 +18,28 @@ check WEB_OPTION_LOGIN_URL, there is one | missing check WEB_OPTION_DISABLED_EXTENSIONS, in the line before, there is one | too much -sed -i "s/\;/\` \+\n\`/g" 4.0.5-5.0.0.adoc make each envvar a single line for ease of readability -sed -i "s/\`|/\`\n|/g" 4.0.5-5.0.0.adoc put the next | after the last envvar in a line into a new line -sed -i "s/|| \`/|\n|\`/g" 4.0.5-5.0.0.adoc split the || before an envvar into two lines -sed -i "s/services\/(.*)\//xref\:\{s-path\}\/\1\.adoc/g" 4.0.5-5.0.0.adoc make "service" a xref +sed -i "s/\;/\` \+\n\`/g" 4.0.0-5.0.0.adoc make each envvar a single line for ease of readability +sed -i "s/\`|/\`\n|/g" 4.0.0-5.0.0.adoc put the next | after the last envvar in a line into a new line +sed -i "s/|| \`/|\n|\`/g" 4.0.0-5.0.0.adoc split the || before an envvar into two lines +sed -i "s/services\/(.*)\//xref\:\{s-path\}\/\1\.adoc/g" 4.0.0-5.0.0.adoc make "service" a xref -sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/config.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc -sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/reva.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc -sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/debug.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc -sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/log.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc -sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/tracing.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc -sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/cache.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc -sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/content.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc -sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/search.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc -sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/options.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.5-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/config.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.0-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/reva.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.0-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/debug.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.0-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/log.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.0-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/tracing.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.0-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/cache.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.0-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/content.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.0-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/search.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.0-5.0.0.adoc +sed -i "s/\(services\/\)\(.*\)\(\/pkg\/config\/options.go\)/xref\:\{s-path\}\/\2.adoc\[\2\]/g" 4.0.0-5.0.0.adoc services/antivirus/pkg/config/config.go --> xref:{s-path}/antivirus.adoc[antivirus] change: ocis-pkg/shared/shared_types.go --> Available in multiple services -sed -i "s/\]|/\]\n|/g" 4.0.5-5.0.0.adoc put tab separator of service and envvar into a new line -sed -i "s/|\`/| \`/g" 4.0.5-5.0.0.adoc |`OCIS --> | `OCIS +sed -i "s/\]|/\]\n|/g" 4.0.0-5.0.0.adoc put tab separator of service and envvar into a new line +sed -i "s/|\`/| \`/g" 4.0.0-5.0.0.adoc |`OCIS --> | `OCIS -sed -i 's/\.\./\./g' 4.0.5-5.0.0.adoc remove trailing double dots in sentences +sed -i 's/\.\./\./g' 4.0.0-5.0.0.adoc remove trailing double dots in sentences manually fix (remove) any double occurrences if a service like clientlog, auth-service, graph, ocm, policies, postprocessing, search, sse. this is because the origin path points to different go routines inside the same service. From 24da3631db675a05254f658d3a0d9bbdc703d188 Mon Sep 17 00:00:00 2001 From: Sawjan Gurung Date: Wed, 7 Feb 2024 17:44:55 +0545 Subject: [PATCH 045/112] [tests-only] API test coverage content-disposition header with filename having quotes (#8390) * extend content-dispostion tests * extend content-dispostion tests --- .../downloadFile.feature | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature b/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature index e60e23898..a028ceada 100644 --- a/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature +++ b/tests/acceptance/features/coreApiWebdavOperations/downloadFile.feature @@ -280,17 +280,47 @@ Feature: download file | X-XSS-Protection | 1; mode=block | And the downloaded content should be "test file" Examples: - | dav-path-version | file | - | old | textfile.txt | - | old | comma,.txt | - | new | textfile.txt | - | new | comma,.txt | + | dav-path-version | file | + | old | textfile.txt | + | old | comma,.txt | + | old | 'quote'single'.txt | + | new | textfile.txt | + | new | comma,.txt | + | new | 'quote'single'.txt | @skipOnRevaMaster Examples: - | dav-path-version | file | - | spaces | textfile.txt | - | spaces | comma,.txt | + | dav-path-version | file | + | spaces | textfile.txt | + | spaces | comma,.txt | + | spaces | 'quote'single'.txt | + + @smokeTest @issue-8361 + Scenario Outline: downloading a file should serve security headers (file with doubel quotes) + Given using DAV path + And user "Alice" has uploaded file with content "test file" to '/"quote"double".txt' + When user "Alice" downloads file '/"quote"double".txt' using the WebDAV API + Then the HTTP status code should be "200" + And the following headers should be set + | header | value | + | Content-Disposition | attachment; filename*=UTF-8''""quote"double".txt"; filename=""quote"double".txt" | + | Content-Security-Policy | default-src 'none'; | + | X-Content-Type-Options | nosniff | + | X-Download-Options | noopen | + | X-Frame-Options | SAMEORIGIN | + | X-Permitted-Cross-Domain-Policies | none | + | X-Robots-Tag | none | + | X-XSS-Protection | 1; mode=block | + And the downloaded content should be "test file" + Examples: + | dav-path-version | + | old | + | new | + + @skipOnRevaMaster + Examples: + | dav-path-version | + | spaces | Scenario: download a zero byte size file From 571af9ecce634d8b7dd64c9a5db707b4ea240b11 Mon Sep 17 00:00:00 2001 From: Sawjan Gurung Date: Wed, 7 Feb 2024 18:23:43 +0545 Subject: [PATCH 046/112] [tests-only] add API test coverage for issue-8242 (#8295) * add test coverage for issue-8242 * add test coverage for issue-8242 --- .../features/bootstrap/FeatureContext.php | 9 ++ .../moveReceivedShare.feature | 137 +++++++++++++++--- 2 files changed, 128 insertions(+), 18 deletions(-) diff --git a/tests/acceptance/features/bootstrap/FeatureContext.php b/tests/acceptance/features/bootstrap/FeatureContext.php index 1b7422d5f..8ac2409b4 100644 --- a/tests/acceptance/features/bootstrap/FeatureContext.php +++ b/tests/acceptance/features/bootstrap/FeatureContext.php @@ -1218,6 +1218,7 @@ class FeatureContext extends BehatVariablesContext { */ public function userSendsHTTPMethodToUrl(string $user, string $verb, string $url): void { $user = $this->getActualUsername($user); + $url = $this->substituteInLineCodes($url, $user); $this->setResponse($this->sendingToWithDirectUrl($user, $verb, $url, null)); } @@ -2879,6 +2880,14 @@ class FeatureContext extends BehatVariablesContext { "function" => [$this, "getGroupIdByGroupName"], "parameter" => [$group] + ], + [ + "code" => "%shares_drive_id%", + "function" => [ + $this->spacesContext, + "getSpaceIdByName" + ], + "parameter" => [$user, "Shares"] ] ); } diff --git a/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature b/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature index 9c59063c4..8ee20c53b 100644 --- a/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature +++ b/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature @@ -9,30 +9,131 @@ Feature: sharing | Brian | | Carol | + @issue-8242 + Scenario Outline: sharer renames the shared item (old/new webdav) + Given user "Alice" has uploaded file with content "foo" to "sharefile.txt" + And using DAV path + And user "Alice" has shared file "sharefile.txt" with user "Brian" + And user "Alice" has shared file "sharefile.txt" with user "Carol" + When user "Alice" moves file "sharefile.txt" to "renamedsharefile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "renamedsharefile.txt" should exist + And as "Brian" file "Shares/sharefile.txt" should exist + And as "Carol" file "Shares/sharefile.txt" should exist + When user "Alice" sends HTTP method "PROPFIND" to URL "" + Then the HTTP status code should be "207" + And as user "Alice" the value of the item "//oc:name" of path "/renamedsharefile.txt" in the response should be "renamedsharefile.txt" + And as user "Alice" the value of the item "//d:displayname" of path "/renamedsharefile.txt" in the response should be "renamedsharefile.txt" + When user "Brian" sends HTTP method "PROPFIND" to URL "/Shares" + Then the HTTP status code should be "207" + And as user "Brian" the value of the item "//oc:name" of path "/Shares/sharefile.txt" in the response should be "sharefile.txt" + And as user "Brian" the value of the item "//d:displayname" of path "/Shares/sharefile.txt" in the response should be "sharefile.txt" + When user "Carol" sends HTTP method "PROPFIND" to URL "/Shares" + Then the HTTP status code should be "207" + And as user "Carol" the value of the item "//oc:name" of path "/Shares/sharefile.txt" in the response should be "sharefile.txt" + And as user "Carol" the value of the item "//d:displayname" of path "/Shares/sharefile.txt" in the response should be "sharefile.txt" + Examples: + | dav-path-version | dav-path | + | old | /remote.php/webdav | + | new | /remote.php/dav/files/%username% | + | new | /dav/files/%username% | - Scenario: keep user/group shares when the user renames the share within the Shares folder + @issue-8242 + Scenario Outline: sharer renames the shared item (spaces webdav) + Given user "Alice" has uploaded file with content "foo" to "sharefile.txt" + And user "Alice" has shared file "sharefile.txt" with user "Brian" + And user "Alice" has shared file "sharefile.txt" with user "Carol" + When user "Alice" moves file "sharefile.txt" to "renamedsharefile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Alice" file "renamedsharefile.txt" should exist + And as "Brian" file "Shares/sharefile.txt" should exist + And as "Carol" file "Shares/sharefile.txt" should exist + And using spaces DAV path + When user "Alice" sends HTTP method "PROPFIND" to URL "" + Then the HTTP status code should be "207" + And as user "Alice" the value of the item "//oc:name" of path "/renamedsharefile.txt" in the response should be "renamedsharefile.txt" + And as user "Alice" the value of the item "//d:displayname" of path "/renamedsharefile.txt" in the response should be "renamedsharefile.txt" + When user "Brian" sends HTTP method "PROPFIND" to URL "" + Then the HTTP status code should be "207" + And as user "Brian" the value of the item "//oc:name" of path "/sharefile.txt" in the response should be "sharefile.txt" + And as user "Brian" the value of the item "//d:displayname" of path "/sharefile.txt" in the response should be "sharefile.txt" + When user "Carol" sends HTTP method "PROPFIND" to URL "" + Then the HTTP status code should be "207" + And as user "Carol" the value of the item "//oc:name" of path "/sharefile.txt" in the response should be "sharefile.txt" + And as user "Carol" the value of the item "//d:displayname" of path "/sharefile.txt" in the response should be "sharefile.txt" + Examples: + | dav-path | dav-path-personal | + | /remote.php/dav/spaces/%shares_drive_id% | /remote.php/dav/spaces/%spaceid% | + | /dav/spaces/%shares_drive_id% | /remote.php/dav/spaces/%spaceid% | + + @issue-8242 + Scenario Outline: share receiver renames the shared item (old/new webdav) + Given user "Alice" has uploaded file with content "foo" to "/sharefile.txt" + And using DAV path + And user "Alice" has shared file "sharefile.txt" with user "Brian" + And user "Alice" has shared file "sharefile.txt" with user "Carol" + When user "Carol" moves file "Shares/sharefile.txt" to "Shares/renamedsharefile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Carol" file "Shares/renamedsharefile.txt" should exist + And as "Brian" file "Shares/sharefile.txt" should exist + And as "Alice" file "sharefile.txt" should exist + When user "Carol" sends HTTP method "PROPFIND" to URL "/Shares" + Then the HTTP status code should be "207" + And as user "Carol" the value of the item "//oc:name" of path "/Shares/renamedsharefile.txt" in the response should be "renamedsharefile.txt" + And as user "Carol" the value of the item "//d:displayname" of path "/Shares/renamedsharefile.txt" in the response should be "renamedsharefile.txt" + When user "Alice" sends HTTP method "PROPFIND" to URL "" + Then the HTTP status code should be "207" + And as user "Alice" the value of the item "//oc:name" of path "/sharefile.txt" in the response should be "sharefile.txt" + And as user "Alice" the value of the item "//d:displayname" of path "/sharefile.txt" in the response should be "sharefile.txt" + When user "Brian" sends HTTP method "PROPFIND" to URL "/Shares" + Then the HTTP status code should be "207" + And as user "Brian" the value of the item "//oc:name" of path "/Shares/sharefile.txt" in the response should be "sharefile.txt" + And as user "Brian" the value of the item "//d:displayname" of path "/Shares/sharefile.txt" in the response should be "sharefile.txt" + Examples: + | dav-path-version | dav-path | + | old | /remote.php/webdav | + | new | /remote.php/dav/files/%username% | + | new | /dav/files/%username% | + + @issue-8242 + Scenario Outline: share receiver renames the shared item (spaces webdav) + Given user "Alice" has uploaded file with content "foo" to "/sharefile.txt" + And user "Alice" has shared file "sharefile.txt" with user "Brian" + And user "Alice" has shared file "sharefile.txt" with user "Carol" + When user "Carol" moves file "Shares/sharefile.txt" to "Shares/renamedsharefile.txt" using the WebDAV API + Then the HTTP status code should be "201" + And as "Carol" file "Shares/renamedsharefile.txt" should exist + And as "Brian" file "Shares/sharefile.txt" should exist + And as "Alice" file "sharefile.txt" should exist + And using spaces DAV path + When user "Carol" sends HTTP method "PROPFIND" to URL "" + Then the HTTP status code should be "207" + And as user "Carol" the value of the item "//oc:name" of path "/renamedsharefile.txt" in the response should be "renamedsharefile.txt" + And as user "Carol" the value of the item "//d:displayname" of path "/renamedsharefile.txt" in the response should be "renamedsharefile.txt" + When user "Alice" sends HTTP method "PROPFIND" to URL "" + Then the HTTP status code should be "207" + And as user "Alice" the value of the item "//oc:name" of path "/sharefile.txt" in the response should be "sharefile.txt" + And as user "Alice" the value of the item "//d:displayname" of path "/sharefile.txt" in the response should be "sharefile.txt" + When user "Brian" sends HTTP method "PROPFIND" to URL "" + Then the HTTP status code should be "207" + And as user "Brian" the value of the item "//oc:name" of path "/sharefile.txt" in the response should be "sharefile.txt" + And as user "Brian" the value of the item "//d:displayname" of path "/sharefile.txt" in the response should be "sharefile.txt" + Examples: + | dav-path | dav-path-personal | + | /remote.php/dav/spaces/%shares_drive_id% | /remote.php/dav/spaces/%spaceid% | + | /dav/spaces/%shares_drive_id% | /remote.php/dav/spaces/%spaceid% | + + + Scenario: keep group share when the one user renames the share and the user is deleted Given group "grp1" has been created And user "Brian" has been added to group "grp1" And user "Carol" has been added to group "grp1" And user "Alice" has created folder "/TMP" - When user "Alice" shares folder "TMP" with group "grp1" using the sharing API - And user "Brian" moves folder "/Shares/TMP" to "/Shares/new" using the WebDAV API + And user "Alice" has shared folder "TMP" with group "grp1" + When user "Carol" moves folder "/Shares/TMP" to "/Shares/new" using the WebDAV API And the administrator deletes user "Carol" using the provisioning API - Then the OCS status code of responses on all endpoints should be "100" - And the HTTP status code of responses on each endpoint should be "200, 201, 204" respectively - And user "Brian" should see the following elements - | /Shares/new/| - - - Scenario: keep user shared file name same after one of recipient has renamed the file inside the Shares folder - Given user "Alice" has uploaded file with content "foo" to "/sharefile.txt" - And user "Alice" has shared file "/sharefile.txt" with user "Brian" - And user "Alice" has shared file "/sharefile.txt" with user "Carol" - When user "Carol" moves file "/Shares/sharefile.txt" to "/Shares/renamedsharefile.txt" using the WebDAV API - Then the HTTP status code should be "201" - And as "Carol" file "/Shares/renamedsharefile.txt" should exist - And as "Alice" file "/sharefile.txt" should exist - And as "Brian" file "/Shares/sharefile.txt" should exist + Then the HTTP status code of responses on each endpoint should be "201, 204" respectively + And as "Alice" file "Shares/TMP" should not exist Scenario: receiver renames a received share with share, read, change permissions inside the Shares folder From b12cff1016a8e7ad0a93ab4511d21a29a0a24ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 7 Feb 2024 15:44:33 +0100 Subject: [PATCH 047/112] fix: properly check expiry and verify signature of signed urls (#8385) fix: signed url expiry validation only checks for expiry and not for used before --- changelog/unreleased/fix-signed-url-expiry.md | 5 + services/proxy/pkg/command/server.go | 1 + .../proxy/pkg/middleware/signed_url_auth.go | 97 ++++++--- .../pkg/middleware/signed_url_auth_test.go | 192 ++++++++++++++---- 4 files changed, 221 insertions(+), 74 deletions(-) create mode 100644 changelog/unreleased/fix-signed-url-expiry.md diff --git a/changelog/unreleased/fix-signed-url-expiry.md b/changelog/unreleased/fix-signed-url-expiry.md new file mode 100644 index 000000000..9b0cdf3da --- /dev/null +++ b/changelog/unreleased/fix-signed-url-expiry.md @@ -0,0 +1,5 @@ +Bugfix: signed url verification + +Signed urls now expire properly + +https://github.com/owncloud/ocis/pull/8385 diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index e917f0ef5..3363f048c 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -374,6 +374,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, UserProvider: userProvider, UserRoleAssigner: roleAssigner, Store: storeClient, + Now: time.Now, }) return alice.New( diff --git a/services/proxy/pkg/middleware/signed_url_auth.go b/services/proxy/pkg/middleware/signed_url_auth.go index a6bc3a703..b3c842a26 100644 --- a/services/proxy/pkg/middleware/signed_url_auth.go +++ b/services/proxy/pkg/middleware/signed_url_auth.go @@ -27,6 +27,7 @@ const ( _paramOCDate = "OC-Date" _paramOCExpires = "OC-Expires" _paramOCVerb = "OC-Verb" + _paramOCAlgo = "OC-Algo" ) var ( @@ -46,6 +47,7 @@ type SignedURLAuthenticator struct { UserProvider backend.UserBackend UserRoleAssigner userroles.UserRoleAssigner Store storesvc.StoreService + Now func() time.Time } func (m SignedURLAuthenticator) shouldServe(req *http.Request) bool { @@ -58,30 +60,30 @@ func (m SignedURLAuthenticator) shouldServe(req *http.Request) bool { func (m SignedURLAuthenticator) validate(req *http.Request) (err error) { query := req.URL.Query() - if ok, err := m.allRequiredParametersArePresent(query); !ok { + if err := m.allRequiredParametersArePresent(query); err != nil { return err } - if ok, err := m.requestMethodMatches(req.Method, query); !ok { + if err := m.requestMethodMatches(req.Method, query); err != nil { return err } - if ok, err := m.requestMethodIsAllowed(req.Method); !ok { + if err := m.requestMethodIsAllowed(req.Method); err != nil { return err } - if expired, err := m.urlIsExpired(query, time.Now); expired { + if err = m.urlIsExpired(query); err != nil { return err } - if ok, err := m.signatureIsValid(req); !ok { + if err := m.signatureIsValid(req); err != nil { return err } return nil } -func (m SignedURLAuthenticator) allRequiredParametersArePresent(query url.Values) (ok bool, err error) { +func (m SignedURLAuthenticator) allRequiredParametersArePresent(query url.Values) (err error) { // check if required query parameters exist in given request query parameters // OC-Signature - the computed signature - server will verify the request upon this REQUIRED // OC-Credential - defines the user scope (shall we use the owncloud user id here - this might leak internal data ....) REQUIRED @@ -90,23 +92,23 @@ func (m SignedURLAuthenticator) allRequiredParametersArePresent(query url.Values // TODO OC-Verb - defines for which http verb the request is valid - defaults to GET OPTIONAL for _, p := range _requiredParams { if query.Get(p) == "" { - return false, fmt.Errorf("required %s parameter not found", p) + return fmt.Errorf("required %s parameter not found", p) } } - return true, nil + return nil } -func (m SignedURLAuthenticator) requestMethodMatches(meth string, query url.Values) (ok bool, err error) { +func (m SignedURLAuthenticator) requestMethodMatches(meth string, query url.Values) (err error) { // check if given url query parameter OC-Verb matches given request method if !strings.EqualFold(meth, query.Get(_paramOCVerb)) { - return false, errors.New("required OC-Verb parameter did not match request method") + return errors.New("required OC-Verb parameter did not match request method") } - return true, nil + return nil } -func (m SignedURLAuthenticator) requestMethodIsAllowed(meth string) (ok bool, err error) { +func (m SignedURLAuthenticator) requestMethodIsAllowed(meth string) (err error) { // check if given request method is allowed methodIsAllowed := false for _, am := range m.PreSignedURLConfig.AllowedHTTPMethods { @@ -117,50 +119,81 @@ func (m SignedURLAuthenticator) requestMethodIsAllowed(meth string) (ok bool, er } if !methodIsAllowed { - return false, errors.New("request method is not listed in PreSignedURLConfig AllowedHTTPMethods") + return errors.New("request method is not listed in PreSignedURLConfig AllowedHTTPMethods") } - return true, nil + return nil } -func (m SignedURLAuthenticator) urlIsExpired(query url.Values, now func() time.Time) (expired bool, err error) { +func (m SignedURLAuthenticator) urlIsExpired(query url.Values) (err error) { // check if url is expired by checking if given date (OC-Date) + expires in seconds (OC-Expires) is after now validFrom, err := time.Parse(time.RFC3339, query.Get(_paramOCDate)) if err != nil { - return true, err + return err } requestExpiry, err := time.ParseDuration(query.Get(_paramOCExpires) + "s") if err != nil { - return true, err + return err } validTo := validFrom.Add(requestExpiry) + if !(m.Now().Before(validTo)) { + return errors.New("URL is expired") + } - return !(now().After(validFrom) && now().Before(validTo)), nil + return nil } -func (m SignedURLAuthenticator) signatureIsValid(req *http.Request) (ok bool, err error) { - u := revactx.ContextMustGetUser(req.Context()) - signingKey, err := m.getSigningKey(req.Context(), u.Id.OpaqueId) +func (m SignedURLAuthenticator) signatureIsValid(req *http.Request) (err error) { + c := revactx.ContextMustGetUser(req.Context()) + signingKey, err := m.getSigningKey(req.Context(), c.Id.OpaqueId) if err != nil { m.Logger.Error().Err(err).Msg("could not retrieve signing key") - return false, err + return err } if len(signingKey) == 0 { m.Logger.Error().Err(err).Msg("signing key empty") - return false, err + return err } - q := req.URL.Query() - signature := q.Get(_paramOCSignature) - q.Del(_paramOCSignature) - req.URL.RawQuery = q.Encode() - url := req.URL.String() - if !req.URL.IsAbs() { - url = "https://" + req.Host + url // TODO where do we get the scheme from + u := m.buildUrlToSign(req) + computedSignature := m.createSignature(u, signingKey) + signatureInURL := req.URL.Query().Get(_paramOCSignature) + if computedSignature == signatureInURL { + return nil } + return fmt.Errorf("signature mismatch: expected %s != actual %s", computedSignature, signatureInURL) +} - return m.createSignature(url, signingKey) == signature, nil +func (m SignedURLAuthenticator) buildUrlToSign(req *http.Request) string { + q := req.URL.Query() + + // only params required for signing + signParameters := make(url.Values) + signParameters.Add(_paramOCCredential, q.Get(_paramOCCredential)) + signParameters.Add(_paramOCDate, q.Get(_paramOCDate)) + signParameters.Add(_paramOCExpires, q.Get(_paramOCExpires)) + signParameters.Add(_paramOCVerb, q.Get(_paramOCVerb)) + + // remaining query params + q.Del(_paramOCAlgo) + q.Del(_paramOCCredential) + q.Del(_paramOCDate) + q.Del(_paramOCExpires) + q.Del(_paramOCSignature) + q.Del(_paramOCVerb) + + url := *req.URL + if len(q) == 0 { + url.RawQuery = signParameters.Encode() + } else { + url.RawQuery = strings.Join([]string{q.Encode(), signParameters.Encode()}, "&") + } + u := url.String() + if !url.IsAbs() { + u = "https://" + req.Host + u // TODO where do we get the scheme + } + return u } func (m SignedURLAuthenticator) createSignature(url string, signingKey []byte) string { @@ -223,10 +256,12 @@ func (m SignedURLAuthenticator) Authenticate(r *http.Request) (*http.Request, bo Err(err). Str("authenticator", "signed_url"). Str("path", r.URL.Path). + Str("url", r.URL.String()). Msg("Could not get user by claim") return nil, false } + // TODO: set user in context m.Logger.Debug(). Str("authenticator", "signed_url"). Str("path", r.URL.Path). diff --git a/services/proxy/pkg/middleware/signed_url_auth_test.go b/services/proxy/pkg/middleware/signed_url_auth_test.go index 35f84e665..a14edc01f 100644 --- a/services/proxy/pkg/middleware/signed_url_auth_test.go +++ b/services/proxy/pkg/middleware/signed_url_auth_test.go @@ -1,6 +1,14 @@ package middleware import ( + "context" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + revactx "github.com/cs3org/reva/v2/pkg/ctx" + storemsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/store/v0" + v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "github.com/stretchr/testify/assert" + "go-micro.dev/v4/client" "net/http/httptest" "testing" "time" @@ -34,21 +42,23 @@ func TestSignedURLAuth_allRequiredParametersPresent(t *testing.T) { pua := SignedURLAuthenticator{} baseURL := "https://example.com/example.jpg?" tests := []struct { - params string - expected bool + params string + errorMessage string }{ - {"OC-Signature=something&OC-Credential=something&OC-Date=something&OC-Expires=something&OC-Verb=something", true}, - {"OC-Credential=something&OC-Date=something&OC-Expires=something&OC-Verb=something", false}, - {"OC-Signature=something&OC-Date=something&OC-Expires=something&OC-Verb=something", false}, - {"OC-Signature=something&OC-Credential=something&OC-Expires=something&OC-Verb=something", false}, - {"OC-Signature=something&OC-Credential=something&OC-Date=something&OC-Verb=something", false}, - {"OC-Signature=something&OC-Credential=something&OC-Date=something&OC-Expires=something", false}, + {"OC-Signature=something&OC-Credential=something&OC-Date=something&OC-Expires=something&OC-Verb=something", ""}, + {"OC-Credential=something&OC-Date=something&OC-Expires=something&OC-Verb=something", "required OC-Signature parameter not found"}, + {"OC-Signature=something&OC-Date=something&OC-Expires=something&OC-Verb=something", "required OC-Credential parameter not found"}, + {"OC-Signature=something&OC-Credential=something&OC-Expires=something&OC-Verb=something", "required OC-Date parameter not found"}, + {"OC-Signature=something&OC-Credential=something&OC-Date=something&OC-Verb=something", "required OC-Expires parameter not found"}, + {"OC-Signature=something&OC-Credential=something&OC-Date=something&OC-Expires=something", "required OC-Verb parameter not found"}, } for _, tt := range tests { r := httptest.NewRequest("", baseURL+tt.params, nil) - ok, _ := pua.allRequiredParametersArePresent(r.URL.Query()) - if ok != tt.expected { - t.Errorf("with %s expected %t got %t", tt.params, tt.expected, ok) + err := pua.allRequiredParametersArePresent(r.URL.Query()) + if tt.errorMessage != "" { + assert.EqualError(t, err, tt.errorMessage, tt.params) + } else { + assert.Nil(t, err) } } } @@ -56,20 +66,22 @@ func TestSignedURLAuth_allRequiredParametersPresent(t *testing.T) { func TestSignedURLAuth_requestMethodMatches(t *testing.T) { pua := SignedURLAuthenticator{} tests := []struct { - method string - url string - expected bool + method string + url string + errorMessage string }{ - {"GET", "https://example.com/example.jpg?OC-Verb=GET", true}, - {"GET", "https://example.com/example.jpg?OC-Verb=get", true}, - {"POST", "https://example.com/example.jpg?OC-Verb=GET", false}, + {"GET", "https://example.com/example.jpg?OC-Verb=GET", ""}, + {"GET", "https://example.com/example.jpg?OC-Verb=get", ""}, + {"POST", "https://example.com/example.jpg?OC-Verb=GET", "required OC-Verb parameter did not match request method"}, } for _, tt := range tests { r := httptest.NewRequest(tt.method, tt.url, nil) - ok, _ := pua.requestMethodMatches(r.Method, r.URL.Query()) - if ok != tt.expected { - t.Errorf("with method %s and url %s expected %t got %t", tt.method, tt.url, tt.expected, ok) + err := pua.requestMethodMatches(r.Method, r.URL.Query()) + if tt.errorMessage != "" { + assert.EqualError(t, err, tt.errorMessage, tt.url) + } else { + assert.Nil(t, err) } } } @@ -77,50 +89,62 @@ func TestSignedURLAuth_requestMethodMatches(t *testing.T) { func TestSignedURLAuth_requestMethodIsAllowed(t *testing.T) { pua := SignedURLAuthenticator{} tests := []struct { - method string - allowed []string - expected bool + method string + allowed []string + errorMessage string }{ - {"GET", []string{}, false}, - {"GET", []string{"POST"}, false}, - {"GET", []string{"GET"}, true}, - {"GET", []string{"get"}, true}, - {"GET", []string{"POST", "GET"}, true}, + {"GET", []string{}, "request method is not listed in PreSignedURLConfig AllowedHTTPMethods"}, + {"GET", []string{"POST"}, "request method is not listed in PreSignedURLConfig AllowedHTTPMethods"}, + {"GET", []string{"GET"}, ""}, + {"GET", []string{"get"}, ""}, + {"GET", []string{"POST", "GET"}, ""}, } for _, tt := range tests { pua.PreSignedURLConfig.AllowedHTTPMethods = tt.allowed - ok, _ := pua.requestMethodIsAllowed(tt.method) - - if ok != tt.expected { - t.Errorf("with method %s and allowed methods %s expected %t got %t", tt.method, tt.allowed, tt.expected, ok) + err := pua.requestMethodIsAllowed(tt.method) + if tt.errorMessage != "" { + assert.EqualError(t, err, tt.errorMessage) + } else { + assert.Nil(t, err) } } } func TestSignedURLAuth_urlIsExpired(t *testing.T) { - pua := SignedURLAuthenticator{} nowFunc := func() time.Time { t, _ := time.Parse(time.RFC3339, "2020-02-02T12:30:00.000Z") return t } + pua := SignedURLAuthenticator{ + Now: nowFunc, + } tests := []struct { - url string - isExpired bool + url string + errorMessage string }{ - {"http://example.com/example.jpg?OC-Date=2020-02-02T12:29:00.000Z&OC-Expires=61", false}, - {"http://example.com/example.jpg?OC-Date=2020-02-02T12:29:00.000Z&OC-Expires=invalid", true}, - {"http://example.com/example.jpg?OC-Date=2020-02-02T12:29:00.000Z&OC-Expires=59", true}, - {"http://example.com/example.jpg?OC-Date=2020-02-03T12:29:00.000Z&OC-Expires=59", true}, - {"http://example.com/example.jpg?OC-Date=2020-02-01T12:29:00.000Z&OC-Expires=59", true}, + // a valid signed url + {"http://example.com/example.jpg?OC-Date=2020-02-02T12:29:00.000Z&OC-Expires=61", ""}, + // invalid expiry + {"http://example.com/example.jpg?OC-Date=2020-02-02T12:29:00.000Z&OC-Expires=invalid", "time: invalid duration \"invalids\""}, + // wrong date format on OC-Date + {"http://example.com/example.jpg?OC-Date=2020-02-02TTT12:29:00.000Z&OC-Expires=5", "parsing time \"2020-02-02TTT12:29:00.000Z\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"TT12:29:00.000Z\" as \"15\""}, + // expired - 12:29:00 + 59s < 12:30 + {"http://example.com/example.jpg?OC-Date=2020-02-02T12:29:00.000Z&OC-Expires=59", "URL is expired"}, + // expired - basically url was created yesterday + {"http://example.com/example.jpg?OC-Date=2020-02-01T12:29:00.000Z&OC-Expires=59", "URL is expired"}, + // future OC-Date - also valid now + {"http://example.com/example.jpg?OC-Date=2020-02-03T12:29:00.000Z&OC-Expires=59", ""}, } for _, tt := range tests { r := httptest.NewRequest("", tt.url, nil) - expired, _ := pua.urlIsExpired(r.URL.Query(), nowFunc) - if expired != tt.isExpired { - t.Errorf("with %s expected %t got %t", tt.url, tt.isExpired, expired) + err := pua.urlIsExpired(r.URL.Query()) + if tt.errorMessage != "" { + assert.EqualError(t, err, tt.errorMessage, tt.url) + } else { + assert.Nil(t, err) } } } @@ -134,3 +158,85 @@ func TestSignedURLAuth_createSignature(t *testing.T) { t.Fail() } } + +type MyStoreService struct { +} + +func (s MyStoreService) Read(_ context.Context, _ *v0.ReadRequest, _ ...client.CallOption) (*v0.ReadResponse, error) { + r := v0.ReadResponse{ + Records: []*storemsg.Record{{ + Key: "foo", + Value: []byte("1234567890"), + Expiry: 0, + Metadata: nil, + }}, + } + return &r, nil +} +func (s MyStoreService) Write(_ context.Context, _ *v0.WriteRequest, _ ...client.CallOption) (*v0.WriteResponse, error) { + return nil, nil +} +func (s MyStoreService) Delete(_ context.Context, _ *v0.DeleteRequest, _ ...client.CallOption) (*v0.DeleteResponse, error) { + return nil, nil +} +func (s MyStoreService) List(_ context.Context, _ *v0.ListRequest, _ ...client.CallOption) (v0.Store_ListService, error) { + return nil, nil +} +func (s MyStoreService) Databases(_ context.Context, _ *v0.DatabasesRequest, _ ...client.CallOption) (*v0.DatabasesResponse, error) { + return nil, nil +} +func (s MyStoreService) Tables(_ context.Context, _ *v0.TablesRequest, _ ...client.CallOption) (*v0.TablesResponse, error) { + return nil, nil +} + +func TestSignedURLAuth_validate(t *testing.T) { + nowFunc := func() time.Time { + t, _ := time.Parse(time.RFC3339, "2020-02-02T12:30:00.000Z") + return t + } + cfg := config.PreSignedURL{ + AllowedHTTPMethods: []string{"get"}, + Enabled: true, + } + store := MyStoreService{} + pua := SignedURLAuthenticator{ + PreSignedURLConfig: cfg, + Store: store, + Now: nowFunc, + } + + tests := []struct { + now string + url string + errorMessage string + }{ + {"2020-02-02T12:30:00.000Z", "http://example.com/example.jpg?OC-Date=2020-02-02T12:29:00.000Z&OC-Expires=invalid", "required OC-Signature parameter not found"}, + {"2020-02-02T12:30:00.000Z", "http://cloud.example.net/?OC-Credential=alice&OC-Date=2019-05-14T11%3A01%3A58.135Z&OC-Expires=1200&OC-Verb=GET&OC-Signature=f9e53a1ee23caef10f72ec392c1b537317491b687bfdd224c782be197d9ca2b6", "URL is expired"}, + {"2019-05-14T11:02:00.000Z", "http://cloud.example.net/?OC-Credential=alice&OC-Date=2019-05-14T11%3A01%3A58.135Z&OC-Expires=1200&OC-Verb=GET&OC-Signature=f9e53a1ee23caef10f72ec392c1b537317491b687bfdd224c782be197d9ca2b", "signature mismatch: expected f9e53a1ee23caef10f72ec392c1b537317491b687bfdd224c782be197d9ca2b6 != actual f9e53a1ee23caef10f72ec392c1b537317491b687bfdd224c782be197d9ca2b"}, + {"2019-05-14T11:02:00.000Z", "http://cloud.example.net/?OC-Credential=alice&OC-Date=2019-05-14T11%3A01%3A58.135Z&OC-Expires=1200&OC-Verb=GET&OC-Signature=f9e53a1ee23caef10f72ec392c1b537317491b687bfdd224c782be197d9ca2b6", ""}, + {"2019-05-14T11:02:00.000Z", "http://cloud.example.net/?OC-Date=2019-05-14T11%3A01%3A58.135Z&OC-Expires=1200&OC-Verb=GET&OC-Credential=alice&OC-Signature=f9e53a1ee23caef10f72ec392c1b537317491b687bfdd224c782be197d9ca2b6", ""}, + {"2019-05-14T11:02:00.000Z", "http://cloud.example.net/?OC-Algo=PBKDF2%2F10000-SHA512&OC-Date=2019-05-14T11%3A01%3A58.135Z&OC-Expires=1200&OC-Verb=GET&OC-Credential=alice&OC-Signature=f9e53a1ee23caef10f72ec392c1b537317491b687bfdd224c782be197d9ca2b6", ""}, + {"2024-02-07T12:03:11.966Z", "http://localhost:33001/try?id=1&id=2&OC-Credential=user&OC-Date=2024-02-07T12%3A03%3A11.966Z&OC-Expires=2&OC-Verb=GET&OC-Algo=PBKDF2%2F10000-SHA512&OC-Signature=86e21a1efbf0be989a206109cfedf70a22f338dc8995e849ce002032bc6741c5", ""}, + } + + for _, tt := range tests { + u := userpb.User{ + Id: &userpb.UserId{OpaqueId: "useri"}, + DisplayName: "Test User", + } + ctx := revactx.ContextSetUser(context.Background(), &u) + + pua.Now = func() time.Time { + t, _ := time.Parse(time.RFC3339, tt.now) + return t + } + + r := httptest.NewRequest("", tt.url, nil).WithContext(ctx) + err := pua.validate(r) + if tt.errorMessage == "" { + assert.Nil(t, err) + } else { + assert.EqualError(t, err, tt.errorMessage) + } + } +} From 49213ebd6b9d5d44d436c4dbb405209b248f83d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 7 Feb 2024 14:45:12 +0000 Subject: [PATCH 048/112] Automated changelog update [skip ci] --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68f6535c1..4ab8880bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ The following sections list the changes for unreleased. * Bugfix - Remove invalid environment variables: [#8303](https://github.com/owncloud/ocis/pull/8303) * Bugfix - Fix concurrent shares config: [#8317](https://github.com/owncloud/ocis/pull/8317) * Bugfix - Fix Content-Disposition header for downloads: [#8381](https://github.com/owncloud/ocis/pull/8381) +* Bugfix - Signed url verification: [#8385](https://github.com/owncloud/ocis/pull/8385) * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) * Enhancement - Disable the password policy: [#7985](https://github.com/owncloud/ocis/pull/7985) * Enhancement - Update antivirus service: [#8062](https://github.com/owncloud/ocis/pull/8062) @@ -245,6 +246,12 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/8381 https://github.com/cs3org/reva/pull/4498 +* Bugfix - Signed url verification: [#8385](https://github.com/owncloud/ocis/pull/8385) + + Signed urls now expire properly + + https://github.com/owncloud/ocis/pull/8385 + * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) Introduce a new environment variable `IDP_LOGIN_BACKGROUND_URL` that overrides From bb7aec92fa6626febdd6e686609223988fe0e261 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 7 Feb 2024 13:14:33 +0100 Subject: [PATCH 049/112] adapt docs-generate step to pick up env var delta tables Signed-off-by: Christian Richter --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 2e3550266..d5ce622c9 100644 --- a/Makefile +++ b/Makefile @@ -168,10 +168,13 @@ clean: docs-generate: # empty the folders first to only have files that are generated without remnants find docs/services/_includes/ -type f \( -name "*" ! -name ".git*" ! -name "_*" \) -delete || exit 1 + @for mod in $(OCIS_MODULES); do \ $(MAKE) --no-print-directory -C $$mod docs-generate || exit 1; \ done $(MAKE) --no-print-directory -C docs docs-generate || exit 1 + mkdir -p docs/services/_includes/env-var-deltas + cp docs/env-var-deltas/*.adoc docs/services/_includes/env-var-deltas/ .PHONY: ci-go-generate ci-go-generate: From bb1eae89c76878c25344548014e1b5c25e334f37 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 7 Feb 2024 18:18:36 +0100 Subject: [PATCH 050/112] move adocs to a different folder Signed-off-by: Christian Richter --- Makefile | 3 +-- docs/services/_includes/.gitignore | 3 ++- docs/services/_includes/adoc/env-var-deltas/.keep | 0 docs/services/_includes/adoc/env-var-deltas/_index.md | 3 +++ 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 docs/services/_includes/adoc/env-var-deltas/.keep create mode 100644 docs/services/_includes/adoc/env-var-deltas/_index.md diff --git a/Makefile b/Makefile index d5ce622c9..33c3a476c 100644 --- a/Makefile +++ b/Makefile @@ -173,8 +173,7 @@ docs-generate: $(MAKE) --no-print-directory -C $$mod docs-generate || exit 1; \ done $(MAKE) --no-print-directory -C docs docs-generate || exit 1 - mkdir -p docs/services/_includes/env-var-deltas - cp docs/env-var-deltas/*.adoc docs/services/_includes/env-var-deltas/ + cp docs/env-var-deltas/*.adoc docs/services/_includes/adoc/env-var-deltas/ .PHONY: ci-go-generate ci-go-generate: diff --git a/docs/services/_includes/.gitignore b/docs/services/_includes/.gitignore index 02c620656..9d0d8ab10 100644 --- a/docs/services/_includes/.gitignore +++ b/docs/services/_includes/.gitignore @@ -1,3 +1,4 @@ *_configvars.md *-example.yaml -adoc/*adoc \ No newline at end of file +adoc/*adoc +adoc/env-var-deltas/*adoc diff --git a/docs/services/_includes/adoc/env-var-deltas/.keep b/docs/services/_includes/adoc/env-var-deltas/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/services/_includes/adoc/env-var-deltas/_index.md b/docs/services/_includes/adoc/env-var-deltas/_index.md new file mode 100644 index 000000000..b58ce0c82 --- /dev/null +++ b/docs/services/_includes/adoc/env-var-deltas/_index.md @@ -0,0 +1,3 @@ +--- +GeekdocHidden: true +--- \ No newline at end of file From 9414f732d7df04d6236e880e6be1357796c72504 Mon Sep 17 00:00:00 2001 From: ownClouders Date: Thu, 8 Feb 2024 01:10:30 +0000 Subject: [PATCH 051/112] [tx] updated from transifex --- .../pkg/email/l10n/locale/de/LC_MESSAGES/notifications.po | 2 +- .../pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po | 2 +- .../pkg/email/l10n/locale/zh-Hans/LC_MESSAGES/notifications.po | 2 +- .../pkg/email/l10n/locale/zh_CN/LC_MESSAGES/notifications.po | 2 +- .../pkg/service/l10n/locale/en_GB/LC_MESSAGES/userlog.po | 2 +- .../userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po | 2 +- .../userlog/pkg/service/l10n/locale/tr/LC_MESSAGES/userlog.po | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/services/notifications/pkg/email/l10n/locale/de/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/de/LC_MESSAGES/notifications.po index 6d609dd2c..f98e58b8b 100644 --- a/services/notifications/pkg/email/l10n/locale/de/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/de/LC_MESSAGES/notifications.po @@ -13,7 +13,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-19 00:48+0000\n" +"POT-Creation-Date: 2024-02-08 01:09+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Michael Barz , 2023\n" "Language-Team: German (https://app.transifex.com/owncloud-org/teams/6149/de/)\n" diff --git a/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po index baf751ee2..23f39d6e1 100644 --- a/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-19 00:48+0000\n" +"POT-Creation-Date: 2024-02-08 01:09+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Michael Barz , 2023\n" "Language-Team: German (Germany) (https://app.transifex.com/owncloud-org/teams/6149/de_DE/)\n" diff --git a/services/notifications/pkg/email/l10n/locale/zh-Hans/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/zh-Hans/LC_MESSAGES/notifications.po index 30b752ee3..eee02c1c5 100644 --- a/services/notifications/pkg/email/l10n/locale/zh-Hans/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/zh-Hans/LC_MESSAGES/notifications.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-19 00:48+0000\n" +"POT-Creation-Date: 2024-02-08 01:09+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Shouyuan, 2023\n" "Language-Team: Chinese Simplified (https://app.transifex.com/owncloud-org/teams/6149/zh-Hans/)\n" diff --git a/services/notifications/pkg/email/l10n/locale/zh_CN/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/zh_CN/LC_MESSAGES/notifications.po index 4bcf5c6c0..9b19507d2 100644 --- a/services/notifications/pkg/email/l10n/locale/zh_CN/LC_MESSAGES/notifications.po +++ b/services/notifications/pkg/email/l10n/locale/zh_CN/LC_MESSAGES/notifications.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-19 00:48+0000\n" +"POT-Creation-Date: 2024-02-08 01:09+0000\n" "PO-Revision-Date: 2023-04-19 11:11+0000\n" "Last-Translator: Shouyuan, 2023\n" "Language-Team: Chinese (China) (https://app.transifex.com/owncloud-org/teams/6149/zh_CN/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/en_GB/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/en_GB/LC_MESSAGES/userlog.po index 65bcfa993..1dfe325fd 100644 --- a/services/userlog/pkg/service/l10n/locale/en_GB/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/en_GB/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-19 00:48+0000\n" +"POT-Creation-Date: 2024-02-08 01:09+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Andi Chandler , 2023\n" "Language-Team: English (United Kingdom) (https://app.transifex.com/owncloud-org/teams/6149/en_GB/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po index 7438cabed..74981a5c8 100644 --- a/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po @@ -13,7 +13,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-18 00:47+0000\n" +"POT-Creation-Date: 2024-02-08 01:09+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Roman Perekhod, 2023\n" "Language-Team: Russian (https://app.transifex.com/owncloud-org/teams/6149/ru/)\n" diff --git a/services/userlog/pkg/service/l10n/locale/tr/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/tr/LC_MESSAGES/userlog.po index cbfdbc0f6..c2298216d 100644 --- a/services/userlog/pkg/service/l10n/locale/tr/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/tr/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-19 00:48+0000\n" +"POT-Creation-Date: 2024-02-08 01:09+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Begüm Topyıldız , 2023\n" "Language-Team: Turkish (https://app.transifex.com/owncloud-org/teams/6149/tr/)\n" From 59b6232260c640776dcb87c4dc0f699ed2608b0d Mon Sep 17 00:00:00 2001 From: nirajacharya2 <122071597+nirajacharya2@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:12:41 +0545 Subject: [PATCH 052/112] adding test for sharing with user and group (#8346) --- .../apiSharingNg/sharedWithMe.feature | 1358 +++++++++++++++++ 1 file changed, 1358 insertions(+) diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature index c013a7f1a..356b6bb63 100644 --- a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature @@ -1617,3 +1617,1361 @@ Feature: an user gets the resources shared to them } } """ + + + Scenario: sharer shares a file to a group and to a user who is in the shared group + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has uploaded file with content "hello" to "textfile0.txt" + And user "Alice" has created a group "grp1" using the Graph API + And user "Brian" has been added to group "grp1" + And user "Alice" has sent the following share invitation: + | resource | textfile0.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + And user "Alice" has sent the following share invitation: + | resource | textfile0.txt | + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem", + "size" + ], + "properties": { + "@UI.Hidden":{ + "type": "boolean", + "enum": [false] + }, + "@client.synchronize":{ + "type": "boolean", + "enum": [true] + }, + "createdBy": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": [ + "textfile0.txt" + ] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType", + "id" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType" : { + "type": "string", + "enum": ["virtual"] + }, + "id" : { + "type": "string", + "pattern": "%space_id_pattern%" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions", + "size" + ], + "properties": { + "createdBy": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "required": [ + "id", + "displayName" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + }, + "displayName": { + "type": "string", + "enum": [ + "Alice Hansen" + ] + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": [ + "textfile0.txt" + ] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "%space_id_pattern%" + }, + "driveType" : { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "group" + ], + "properties":{ + "group": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": [ + "invitedBy" + ], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + }, + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties":{ + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": [ + "invitedBy" + ], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + ] + } + }, + "size": { + "type": "number", + "enum": [ + 5 + ] + } + } + }, + "size": { + "type": "number", + "enum": [ + 5 + ] + } + } + } + } + } + } + """ + + @issues-8314 + Scenario: sharer shares a file in project space to a group and to a user who is in the shared group file + Given using spaces DAV path + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "textfile0.txt" + And user "Alice" has created a group "grp1" using the Graph API + And user "Brian" has been added to group "grp1" + And user "Alice" has sent the following share invitation: + | resource | textfile0.txt | + | space | new-space | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + And user "Alice" has sent the following share invitation: + | resource | textfile0.txt | + | space | new-space | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem", + "size" + ], + "properties": { + "@UI.Hidden":{ + "type": "boolean", + "enum": [false] + }, + "@client.synchronize":{ + "type": "boolean", + "enum": [true] + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": [ + "textfile0.txt" + ] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType", + "id" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType" : { + "type": "string", + "enum": ["virtual"] + }, + "id" : { + "type": "string", + "pattern": "%space_id_pattern%" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions", + "size" + ], + "properties": { + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": [ + "textfile0.txt" + ] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "%space_id_pattern%" + }, + "driveType" : { + "type": "string", + "enum": ["project"] + } + } + }, + "permissions": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "group" + ], + "properties":{ + "group": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": [ + "invitedBy" + ], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + }, + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties":{ + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": [ + "invitedBy" + ], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + ] + } + }, + "size": { + "type": "number", + "enum": [ + 11 + ] + } + } + }, + "size": { + "type": "number", + "enum": [ + 11 + ] + } + } + } + } + } + } + """ + + + Scenario: sharer shares a folder to a group and to a user who is in the shared group folder + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created folder "folder" + And user "Alice" has created a group "grp1" using the Graph API + And user "Brian" has been added to group "grp1" + And user "Alice" has sent the following share invitation: + | resource | folder | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + And user "Alice" has sent the following share invitation: + | resource | folder | + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem" + ], + "properties": { + "@UI.Hidden":{ + "type": "boolean", + "enum": [false] + }, + "@client.synchronize":{ + "type": "boolean", + "enum": [true] + }, + "createdBy": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "folder": {}, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": [ + "folder" + ] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType", + "id" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType" : { + "type": "string", + "enum": ["virtual"] + }, + "id" : { + "type": "string", + "pattern": "%space_id_pattern%" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions" + ], + "properties": { + "createdBy": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "required": [ + "id", + "displayName" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + }, + "displayName": { + "type": "string", + "enum": [ + "Alice Hansen" + ] + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": {}, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": [ + "folder" + ] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "%space_id_pattern%" + }, + "driveType" : { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "group" + ], + "properties":{ + "group": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": [ + "invitedBy" + ], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + }, + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties":{ + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": [ + "invitedBy" + ], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + ] + } + } + } + } + } + } + } + } + } + """ + + @issues-8314 + Scenario: sharer shares a folder in project space to a group and to a user who is in the shared group + Given using spaces DAV path + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has created a group "grp1" using the Graph API + And user "Alice" has created a folder "folder" in space "new-space" + And user "Brian" has been added to group "grp1" + And user "Alice" has sent the following share invitation: + | resource | folder | + | space | new-space | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + And user "Alice" has sent the following share invitation: + | resource | folder | + | space | new-space | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem" + ], + "properties": { + "@UI.Hidden":{ + "type": "boolean", + "enum": [false] + }, + "@client.synchronize":{ + "type": "boolean", + "enum": [true] + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "folder": {}, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": [ + "folder" + ] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType", + "id" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType" : { + "type": "string", + "enum": ["virtual"] + }, + "id" : { + "type": "string", + "pattern": "%space_id_pattern%" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions" + ], + "properties": { + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "folder": {}, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": [ + "folder" + ] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "%space_id_pattern%" + }, + "driveType" : { + "type": "string", + "enum": ["project"] + } + } + }, + "permissions": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "group" + ], + "properties":{ + "group": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": [ + "invitedBy" + ], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + }, + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties":{ + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": [ + "invitedBy" + ], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + ] + } + } + } + } + } + } + } + } + } + """ \ No newline at end of file From 26eb1d6121720b2f7b523af44e551622cb1d52cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 06:21:22 +0000 Subject: [PATCH 053/112] build(deps): bump golang.org/x/net from 0.20.0 to 0.21.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.21.0. - [Commits](https://github.com/golang/net/compare/v0.20.0...v0.21.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 +- go.sum | 16 +-- vendor/golang.org/x/net/html/token.go | 12 +- vendor/golang.org/x/net/http2/frame.go | 11 +- vendor/golang.org/x/sys/unix/mkerrors.sh | 2 +- vendor/golang.org/x/sys/unix/zerrors_linux.go | 36 ++++- .../x/sys/unix/zerrors_linux_386.go | 3 + .../x/sys/unix/zerrors_linux_amd64.go | 3 + .../x/sys/unix/zerrors_linux_arm.go | 3 + .../x/sys/unix/zerrors_linux_arm64.go | 3 + .../x/sys/unix/zerrors_linux_loong64.go | 3 + .../x/sys/unix/zerrors_linux_mips.go | 3 + .../x/sys/unix/zerrors_linux_mips64.go | 3 + .../x/sys/unix/zerrors_linux_mips64le.go | 3 + .../x/sys/unix/zerrors_linux_mipsle.go | 3 + .../x/sys/unix/zerrors_linux_ppc.go | 3 + .../x/sys/unix/zerrors_linux_ppc64.go | 3 + .../x/sys/unix/zerrors_linux_ppc64le.go | 3 + .../x/sys/unix/zerrors_linux_riscv64.go | 3 + .../x/sys/unix/zerrors_linux_s390x.go | 3 + .../x/sys/unix/zerrors_linux_sparc64.go | 3 + .../x/sys/unix/zsysnum_linux_386.go | 4 + .../x/sys/unix/zsysnum_linux_amd64.go | 3 + .../x/sys/unix/zsysnum_linux_arm.go | 4 + .../x/sys/unix/zsysnum_linux_arm64.go | 4 + .../x/sys/unix/zsysnum_linux_loong64.go | 4 + .../x/sys/unix/zsysnum_linux_mips.go | 4 + .../x/sys/unix/zsysnum_linux_mips64.go | 4 + .../x/sys/unix/zsysnum_linux_mips64le.go | 4 + .../x/sys/unix/zsysnum_linux_mipsle.go | 4 + .../x/sys/unix/zsysnum_linux_ppc.go | 4 + .../x/sys/unix/zsysnum_linux_ppc64.go | 4 + .../x/sys/unix/zsysnum_linux_ppc64le.go | 4 + .../x/sys/unix/zsysnum_linux_riscv64.go | 4 + .../x/sys/unix/zsysnum_linux_s390x.go | 4 + .../x/sys/unix/zsysnum_linux_sparc64.go | 4 + vendor/golang.org/x/sys/unix/ztypes_linux.go | 125 +++++++++--------- .../golang.org/x/sys/windows/env_windows.go | 17 ++- .../x/sys/windows/syscall_windows.go | 3 +- vendor/modules.txt | 8 +- 40 files changed, 245 insertions(+), 97 deletions(-) diff --git a/go.mod b/go.mod index 93ac2cc98..b0952cbaa 100644 --- a/go.mod +++ b/go.mod @@ -94,12 +94,12 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 go.opentelemetry.io/otel/sdk v1.22.0 go.opentelemetry.io/otel/trace v1.23.0 - golang.org/x/crypto v0.18.0 + golang.org/x/crypto v0.19.0 golang.org/x/image v0.15.0 - golang.org/x/net v0.20.0 + golang.org/x/net v0.21.0 golang.org/x/oauth2 v0.16.0 golang.org/x/sync v0.6.0 - golang.org/x/term v0.16.0 + golang.org/x/term v0.17.0 golang.org/x/text v0.14.0 google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe google.golang.org/grpc v1.61.0 @@ -337,7 +337,7 @@ require ( go.uber.org/zap v1.23.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/go.sum b/go.sum index 244e7dbd7..ce4c381a5 100644 --- a/go.sum +++ b/go.sum @@ -2169,8 +2169,8 @@ golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIi golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2315,8 +2315,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2498,8 +2498,8 @@ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2516,8 +2516,8 @@ golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/vendor/golang.org/x/net/html/token.go b/vendor/golang.org/x/net/html/token.go index de67f938a..3c57880d6 100644 --- a/vendor/golang.org/x/net/html/token.go +++ b/vendor/golang.org/x/net/html/token.go @@ -910,9 +910,6 @@ func (z *Tokenizer) readTagAttrKey() { return } switch c { - case ' ', '\n', '\r', '\t', '\f', '/': - z.pendingAttr[0].end = z.raw.end - 1 - return case '=': if z.pendingAttr[0].start+1 == z.raw.end { // WHATWG 13.2.5.32, if we see an equals sign before the attribute name @@ -920,7 +917,9 @@ func (z *Tokenizer) readTagAttrKey() { continue } fallthrough - case '>': + case ' ', '\n', '\r', '\t', '\f', '/', '>': + // WHATWG 13.2.5.33 Attribute name state + // We need to reconsume the char in the after attribute name state to support the / character z.raw.end-- z.pendingAttr[0].end = z.raw.end return @@ -939,6 +938,11 @@ func (z *Tokenizer) readTagAttrVal() { if z.err != nil { return } + if c == '/' { + // WHATWG 13.2.5.34 After attribute name state + // U+002F SOLIDUS (/) - Switch to the self-closing start tag state. + return + } if c != '=' { z.raw.end-- return diff --git a/vendor/golang.org/x/net/http2/frame.go b/vendor/golang.org/x/net/http2/frame.go index c1f6b90dc..e2b298d85 100644 --- a/vendor/golang.org/x/net/http2/frame.go +++ b/vendor/golang.org/x/net/http2/frame.go @@ -1510,13 +1510,12 @@ func (mh *MetaHeadersFrame) checkPseudos() error { } func (fr *Framer) maxHeaderStringLen() int { - v := fr.maxHeaderListSize() - if uint32(int(v)) == v { - return int(v) + v := int(fr.maxHeaderListSize()) + if v < 0 { + // If maxHeaderListSize overflows an int, use no limit (0). + return 0 } - // They had a crazy big number for MaxHeaderBytes anyway, - // so give them unlimited header lengths: - return 0 + return v } // readMetaFrame returns 0 or more CONTINUATION frames from fr and diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index c6492020e..fdcaa974d 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -584,7 +584,7 @@ ccflags="$@" $2 ~ /^KEY_(SPEC|REQKEY_DEFL)_/ || $2 ~ /^KEYCTL_/ || $2 ~ /^PERF_/ || - $2 ~ /^SECCOMP_MODE_/ || + $2 ~ /^SECCOMP_/ || $2 ~ /^SEEK_/ || $2 ~ /^SCHED_/ || $2 ~ /^SPLICE_/ || diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index a5d3ff8df..36bf8399f 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -1785,6 +1785,8 @@ const ( LANDLOCK_ACCESS_FS_REMOVE_FILE = 0x20 LANDLOCK_ACCESS_FS_TRUNCATE = 0x4000 LANDLOCK_ACCESS_FS_WRITE_FILE = 0x2 + LANDLOCK_ACCESS_NET_BIND_TCP = 0x1 + LANDLOCK_ACCESS_NET_CONNECT_TCP = 0x2 LANDLOCK_CREATE_RULESET_VERSION = 0x1 LINUX_REBOOT_CMD_CAD_OFF = 0x0 LINUX_REBOOT_CMD_CAD_ON = 0x89abcdef @@ -2465,6 +2467,7 @@ const ( PR_MCE_KILL_GET = 0x22 PR_MCE_KILL_LATE = 0x0 PR_MCE_KILL_SET = 0x1 + PR_MDWE_NO_INHERIT = 0x2 PR_MDWE_REFUSE_EXEC_GAIN = 0x1 PR_MPX_DISABLE_MANAGEMENT = 0x2c PR_MPX_ENABLE_MANAGEMENT = 0x2b @@ -2669,8 +2672,9 @@ const ( RTAX_FEATURES = 0xc RTAX_FEATURE_ALLFRAG = 0x8 RTAX_FEATURE_ECN = 0x1 - RTAX_FEATURE_MASK = 0xf + RTAX_FEATURE_MASK = 0x1f RTAX_FEATURE_SACK = 0x2 + RTAX_FEATURE_TCP_USEC_TS = 0x10 RTAX_FEATURE_TIMESTAMP = 0x4 RTAX_HOPLIMIT = 0xa RTAX_INITCWND = 0xb @@ -2913,9 +2917,38 @@ const ( SCM_RIGHTS = 0x1 SCM_TIMESTAMP = 0x1d SC_LOG_FLUSH = 0x100000 + SECCOMP_ADDFD_FLAG_SEND = 0x2 + SECCOMP_ADDFD_FLAG_SETFD = 0x1 + SECCOMP_FILTER_FLAG_LOG = 0x2 + SECCOMP_FILTER_FLAG_NEW_LISTENER = 0x8 + SECCOMP_FILTER_FLAG_SPEC_ALLOW = 0x4 + SECCOMP_FILTER_FLAG_TSYNC = 0x1 + SECCOMP_FILTER_FLAG_TSYNC_ESRCH = 0x10 + SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV = 0x20 + SECCOMP_GET_ACTION_AVAIL = 0x2 + SECCOMP_GET_NOTIF_SIZES = 0x3 + SECCOMP_IOCTL_NOTIF_RECV = 0xc0502100 + SECCOMP_IOCTL_NOTIF_SEND = 0xc0182101 + SECCOMP_IOC_MAGIC = '!' SECCOMP_MODE_DISABLED = 0x0 SECCOMP_MODE_FILTER = 0x2 SECCOMP_MODE_STRICT = 0x1 + SECCOMP_RET_ACTION = 0x7fff0000 + SECCOMP_RET_ACTION_FULL = 0xffff0000 + SECCOMP_RET_ALLOW = 0x7fff0000 + SECCOMP_RET_DATA = 0xffff + SECCOMP_RET_ERRNO = 0x50000 + SECCOMP_RET_KILL = 0x0 + SECCOMP_RET_KILL_PROCESS = 0x80000000 + SECCOMP_RET_KILL_THREAD = 0x0 + SECCOMP_RET_LOG = 0x7ffc0000 + SECCOMP_RET_TRACE = 0x7ff00000 + SECCOMP_RET_TRAP = 0x30000 + SECCOMP_RET_USER_NOTIF = 0x7fc00000 + SECCOMP_SET_MODE_FILTER = 0x1 + SECCOMP_SET_MODE_STRICT = 0x0 + SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP = 0x1 + SECCOMP_USER_NOTIF_FLAG_CONTINUE = 0x1 SECRETMEM_MAGIC = 0x5345434d SECURITYFS_MAGIC = 0x73636673 SEEK_CUR = 0x1 @@ -3075,6 +3108,7 @@ const ( SOL_TIPC = 0x10f SOL_TLS = 0x11a SOL_UDP = 0x11 + SOL_VSOCK = 0x11f SOL_X25 = 0x106 SOL_XDP = 0x11b SOMAXCONN = 0x1000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index 4920821cf..42ff8c3c1 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -281,6 +281,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index a0c1e4112..dca436004 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -282,6 +282,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index c63985560..5cca668ac 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -288,6 +288,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index 47cc62e25..d8cae6d15 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -278,6 +278,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index 27ac4a09e..28e39afdc 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -275,6 +275,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index 54694642a..cd66e92cb 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -281,6 +281,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x80 SIOCATMARK = 0x40047307 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index 3adb81d75..c1595eba7 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -281,6 +281,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x80 SIOCATMARK = 0x40047307 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index 2dfe98f0d..ee9456b0d 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -281,6 +281,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x80 SIOCATMARK = 0x40047307 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index f5398f84f..8cfca81e1 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -281,6 +281,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x80 SIOCATMARK = 0x40047307 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index c54f152d6..60b0deb3a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -336,6 +336,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index 76057dc72..f90aa7281 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -340,6 +340,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index e0c3725e2..ba9e01503 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -340,6 +340,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index 18f2813ed..07cdfd6e9 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -272,6 +272,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index 11619d4ec..2f1dd214a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -344,6 +344,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index 396d994da..f40519d90 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -335,6 +335,9 @@ const ( SCM_TIMESTAMPNS = 0x21 SCM_TXTIME = 0x3f SCM_WIFI_STATUS = 0x25 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x400000 SFD_NONBLOCK = 0x4000 SF_FP = 0x38 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go index fcf3ecbdd..0cc3ce496 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go @@ -448,4 +448,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go index f56dc2504..856d92d69 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go @@ -371,4 +371,7 @@ const ( SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go index 974bf2467..8d467094c 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go @@ -412,4 +412,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go index 39a2739e2..edc173244 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go @@ -315,4 +315,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go index cf9c9d77e..445eba206 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go @@ -309,4 +309,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go index 10b7362ef..adba01bca 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go @@ -432,4 +432,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 4450 SYS_CACHESTAT = 4451 SYS_FCHMODAT2 = 4452 + SYS_MAP_SHADOW_STACK = 4453 + SYS_FUTEX_WAKE = 4454 + SYS_FUTEX_WAIT = 4455 + SYS_FUTEX_REQUEUE = 4456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go index cd4d8b4fd..014c4e9c7 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go @@ -362,4 +362,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 5450 SYS_CACHESTAT = 5451 SYS_FCHMODAT2 = 5452 + SYS_MAP_SHADOW_STACK = 5453 + SYS_FUTEX_WAKE = 5454 + SYS_FUTEX_WAIT = 5455 + SYS_FUTEX_REQUEUE = 5456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go index 2c0efca81..ccc97d74d 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go @@ -362,4 +362,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 5450 SYS_CACHESTAT = 5451 SYS_FCHMODAT2 = 5452 + SYS_MAP_SHADOW_STACK = 5453 + SYS_FUTEX_WAKE = 5454 + SYS_FUTEX_WAIT = 5455 + SYS_FUTEX_REQUEUE = 5456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go index a72e31d39..ec2b64a95 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go @@ -432,4 +432,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 4450 SYS_CACHESTAT = 4451 SYS_FCHMODAT2 = 4452 + SYS_MAP_SHADOW_STACK = 4453 + SYS_FUTEX_WAKE = 4454 + SYS_FUTEX_WAIT = 4455 + SYS_FUTEX_REQUEUE = 4456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go index c7d1e3747..21a839e33 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go @@ -439,4 +439,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go index f4d4838c8..c11121ec3 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go @@ -411,4 +411,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go index b64f0e591..909b631fc 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go @@ -411,4 +411,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go index 95711195a..e49bed16e 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go @@ -316,4 +316,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go index f94e943bc..66017d2d3 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go @@ -377,4 +377,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go index ba0c2bc51..47bab18dc 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go @@ -390,4 +390,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index bbf8399ff..dc0c955ee 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -174,7 +174,8 @@ type FscryptPolicyV2 struct { Contents_encryption_mode uint8 Filenames_encryption_mode uint8 Flags uint8 - _ [4]uint8 + Log2_data_unit_size uint8 + _ [3]uint8 Master_key_identifier [16]uint8 } @@ -455,60 +456,63 @@ type Ucred struct { } type TCPInfo struct { - State uint8 - Ca_state uint8 - Retransmits uint8 - Probes uint8 - Backoff uint8 - Options uint8 - Rto uint32 - Ato uint32 - Snd_mss uint32 - Rcv_mss uint32 - Unacked uint32 - Sacked uint32 - Lost uint32 - Retrans uint32 - Fackets uint32 - Last_data_sent uint32 - Last_ack_sent uint32 - Last_data_recv uint32 - Last_ack_recv uint32 - Pmtu uint32 - Rcv_ssthresh uint32 - Rtt uint32 - Rttvar uint32 - Snd_ssthresh uint32 - Snd_cwnd uint32 - Advmss uint32 - Reordering uint32 - Rcv_rtt uint32 - Rcv_space uint32 - Total_retrans uint32 - Pacing_rate uint64 - Max_pacing_rate uint64 - Bytes_acked uint64 - Bytes_received uint64 - Segs_out uint32 - Segs_in uint32 - Notsent_bytes uint32 - Min_rtt uint32 - Data_segs_in uint32 - Data_segs_out uint32 - Delivery_rate uint64 - Busy_time uint64 - Rwnd_limited uint64 - Sndbuf_limited uint64 - Delivered uint32 - Delivered_ce uint32 - Bytes_sent uint64 - Bytes_retrans uint64 - Dsack_dups uint32 - Reord_seen uint32 - Rcv_ooopack uint32 - Snd_wnd uint32 - Rcv_wnd uint32 - Rehash uint32 + State uint8 + Ca_state uint8 + Retransmits uint8 + Probes uint8 + Backoff uint8 + Options uint8 + Rto uint32 + Ato uint32 + Snd_mss uint32 + Rcv_mss uint32 + Unacked uint32 + Sacked uint32 + Lost uint32 + Retrans uint32 + Fackets uint32 + Last_data_sent uint32 + Last_ack_sent uint32 + Last_data_recv uint32 + Last_ack_recv uint32 + Pmtu uint32 + Rcv_ssthresh uint32 + Rtt uint32 + Rttvar uint32 + Snd_ssthresh uint32 + Snd_cwnd uint32 + Advmss uint32 + Reordering uint32 + Rcv_rtt uint32 + Rcv_space uint32 + Total_retrans uint32 + Pacing_rate uint64 + Max_pacing_rate uint64 + Bytes_acked uint64 + Bytes_received uint64 + Segs_out uint32 + Segs_in uint32 + Notsent_bytes uint32 + Min_rtt uint32 + Data_segs_in uint32 + Data_segs_out uint32 + Delivery_rate uint64 + Busy_time uint64 + Rwnd_limited uint64 + Sndbuf_limited uint64 + Delivered uint32 + Delivered_ce uint32 + Bytes_sent uint64 + Bytes_retrans uint64 + Dsack_dups uint32 + Reord_seen uint32 + Rcv_ooopack uint32 + Snd_wnd uint32 + Rcv_wnd uint32 + Rehash uint32 + Total_rto uint16 + Total_rto_recoveries uint16 + Total_rto_time uint32 } type CanFilter struct { @@ -551,7 +555,7 @@ const ( SizeofIPv6MTUInfo = 0x20 SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc - SizeofTCPInfo = 0xf0 + SizeofTCPInfo = 0xf8 SizeofCanFilter = 0x8 SizeofTCPRepairOpt = 0x8 ) @@ -3399,7 +3403,7 @@ const ( DEVLINK_PORT_FN_ATTR_STATE = 0x2 DEVLINK_PORT_FN_ATTR_OPSTATE = 0x3 DEVLINK_PORT_FN_ATTR_CAPS = 0x4 - DEVLINK_PORT_FUNCTION_ATTR_MAX = 0x4 + DEVLINK_PORT_FUNCTION_ATTR_MAX = 0x5 ) type FsverityDigest struct { @@ -4183,7 +4187,8 @@ const ( ) type LandlockRulesetAttr struct { - Access_fs uint64 + Access_fs uint64 + Access_net uint64 } type LandlockPathBeneathAttr struct { @@ -5134,7 +5139,7 @@ const ( NL80211_FREQUENCY_ATTR_GO_CONCURRENT = 0xf NL80211_FREQUENCY_ATTR_INDOOR_ONLY = 0xe NL80211_FREQUENCY_ATTR_IR_CONCURRENT = 0xf - NL80211_FREQUENCY_ATTR_MAX = 0x1b + NL80211_FREQUENCY_ATTR_MAX = 0x1c NL80211_FREQUENCY_ATTR_MAX_TX_POWER = 0x6 NL80211_FREQUENCY_ATTR_NO_10MHZ = 0x11 NL80211_FREQUENCY_ATTR_NO_160MHZ = 0xc @@ -5547,7 +5552,7 @@ const ( NL80211_REGDOM_TYPE_CUSTOM_WORLD = 0x2 NL80211_REGDOM_TYPE_INTERSECTION = 0x3 NL80211_REGDOM_TYPE_WORLD = 0x1 - NL80211_REG_RULE_ATTR_MAX = 0x7 + NL80211_REG_RULE_ATTR_MAX = 0x8 NL80211_REKEY_DATA_AKM = 0x4 NL80211_REKEY_DATA_KCK = 0x2 NL80211_REKEY_DATA_KEK = 0x1 diff --git a/vendor/golang.org/x/sys/windows/env_windows.go b/vendor/golang.org/x/sys/windows/env_windows.go index b8ad19250..d4577a423 100644 --- a/vendor/golang.org/x/sys/windows/env_windows.go +++ b/vendor/golang.org/x/sys/windows/env_windows.go @@ -37,14 +37,17 @@ func (token Token) Environ(inheritExisting bool) (env []string, err error) { return nil, err } defer DestroyEnvironmentBlock(block) - blockp := unsafe.Pointer(block) - for { - entry := UTF16PtrToString((*uint16)(blockp)) - if len(entry) == 0 { - break + size := unsafe.Sizeof(*block) + for *block != 0 { + // find NUL terminator + end := unsafe.Pointer(block) + for *(*uint16)(end) != 0 { + end = unsafe.Add(end, size) } - env = append(env, entry) - blockp = unsafe.Add(blockp, 2*(len(entry)+1)) + + entry := unsafe.Slice(block, (uintptr(end)-uintptr(unsafe.Pointer(block)))/size) + env = append(env, UTF16ToString(entry)) + block = (*uint16)(unsafe.Add(end, size)) } return env, nil } diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index ffb8708cc..6395a031d 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -125,8 +125,7 @@ func UTF16PtrToString(p *uint16) string { for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; n++ { ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*p)) } - - return string(utf16.Decode(unsafe.Slice(p, n))) + return UTF16ToString(unsafe.Slice(p, n)) } func Getpagesize() int { return 4096 } diff --git a/vendor/modules.txt b/vendor/modules.txt index 4aef0ec19..d3129c6cb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2017,7 +2017,7 @@ go.uber.org/zap/internal/color go.uber.org/zap/internal/exit go.uber.org/zap/zapcore go.uber.org/zap/zapgrpc -# golang.org/x/crypto v0.18.0 +# golang.org/x/crypto v0.19.0 ## explicit; go 1.18 golang.org/x/crypto/argon2 golang.org/x/crypto/bcrypt @@ -2067,7 +2067,7 @@ golang.org/x/image/vector golang.org/x/mod/internal/lazyregexp golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/net v0.20.0 +# golang.org/x/net v0.21.0 ## explicit; go 1.18 golang.org/x/net/bpf golang.org/x/net/context @@ -2099,7 +2099,7 @@ golang.org/x/oauth2/internal golang.org/x/sync/errgroup golang.org/x/sync/semaphore golang.org/x/sync/singleflight -# golang.org/x/sys v0.16.0 +# golang.org/x/sys v0.17.0 ## explicit; go 1.18 golang.org/x/sys/cpu golang.org/x/sys/execabs @@ -2110,7 +2110,7 @@ golang.org/x/sys/windows/registry golang.org/x/sys/windows/svc golang.org/x/sys/windows/svc/eventlog golang.org/x/sys/windows/svc/mgr -# golang.org/x/term v0.16.0 +# golang.org/x/term v0.17.0 ## explicit; go 1.18 golang.org/x/term # golang.org/x/text v0.14.0 From 9a1131b763f4d3358798ebdd62d7215593ff3eb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 07:52:45 +0000 Subject: [PATCH 054/112] build(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) from 1.22.0 to 1.23.1. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.22.0...v1.23.1) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 14 +- go.sum | 28 +- vendor/go.opentelemetry.io/otel/CHANGELOG.md | 9 +- .../otel/exporters/otlp/otlptrace/exporter.go | 2 +- .../otlp/otlptrace/otlptracegrpc/doc.go | 2 +- .../internal/otlpconfig/options.go | 20 + .../otlp/otlptrace/otlptracegrpc/options.go | 34 +- .../otel/exporters/otlp/otlptrace/version.go | 2 +- .../otel/sdk/resource/auto.go | 25 +- .../otel/sdk/resource/container.go | 2 +- .../otel/sdk/resource/resource.go | 79 ++-- .../otel/sdk/trace/batch_span_processor.go | 2 +- .../otel/sdk/trace/provider.go | 2 +- .../go.opentelemetry.io/otel/sdk/version.go | 2 +- vendor/go.opentelemetry.io/otel/version.go | 2 +- vendor/go.opentelemetry.io/otel/versions.yaml | 4 +- .../proto/otlp/trace/v1/trace.pb.go | 367 ++++++++++++------ vendor/modules.txt | 14 +- 18 files changed, 415 insertions(+), 195 deletions(-) diff --git a/go.mod b/go.mod index b0952cbaa..8ca8b217d 100644 --- a/go.mod +++ b/go.mod @@ -89,11 +89,11 @@ require ( go.etcd.io/bbolt v1.3.8 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 go.opentelemetry.io/contrib/zpages v0.47.0 - go.opentelemetry.io/otel v1.23.0 + go.opentelemetry.io/otel v1.23.1 go.opentelemetry.io/otel/exporters/jaeger v1.17.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 - go.opentelemetry.io/otel/sdk v1.22.0 - go.opentelemetry.io/otel/trace v1.23.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 + go.opentelemetry.io/otel/sdk v1.23.1 + go.opentelemetry.io/otel/trace v1.23.1 golang.org/x/crypto v0.19.0 golang.org/x/image v0.15.0 golang.org/x/net v0.21.0 @@ -329,9 +329,9 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib v1.0.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.23.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 // indirect + go.opentelemetry.io/otel/metric v1.23.1 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.23.0 // indirect diff --git a/go.sum b/go.sum index ce4c381a5..e52c1c594 100644 --- a/go.sum +++ b/go.sum @@ -2099,27 +2099,27 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1: go.opentelemetry.io/contrib/zpages v0.47.0 h1:ekpdNa2wqOvAfwZIGDIIV02zmR+z08aWPt21KrPJnaU= go.opentelemetry.io/contrib/zpages v0.47.0/go.mod h1:rBeFA/UxnMjRlEGpmClIqzf1mCIKtl7ahjww3wsSdGs= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= -go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E= -go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= -go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo= -go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1/go.mod h1:SEVfdK4IoBnbT2FXNM/k8yC08MrfbhWk3U4ljM8B3HE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 h1:p3A5+f5l9e/kuEBwLOrnpkIDHQFlHmbiVxMURWRK6gQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1/go.mod h1:OClrnXUjBqQbInvjJFjYSnMxBSCXBF8r3b34WqjiIrQ= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= +go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= -go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= -go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= diff --git a/vendor/go.opentelemetry.io/otel/CHANGELOG.md b/vendor/go.opentelemetry.io/otel/CHANGELOG.md index ea1f723e2..5422ca7dc 100644 --- a/vendor/go.opentelemetry.io/otel/CHANGELOG.md +++ b/vendor/go.opentelemetry.io/otel/CHANGELOG.md @@ -8,6 +8,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +## [1.23.1] 2024-02-07 + +### Fixed + +- Register all callbacks passed during observable instrument creation instead of just the last one multiple times in `go.opentelemetry.io/otel/sdk/metric`. (#4888) + ## [1.23.0] 2024-02-06 This release contains the first stable, `v1`, release of the following modules: @@ -2823,7 +2829,8 @@ It contains api and sdk for trace and meter. - CircleCI build CI manifest files. - CODEOWNERS file to track owners of this project. -[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.23.0...HEAD +[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.23.1...HEAD +[1.23.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.1 [1.23.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.0 [1.23.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.0-rc.1 [1.22.0/0.45.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.22.0 diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/exporter.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/exporter.go index b46a38d60..cb41c7d58 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/exporter.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/exporter.go @@ -104,7 +104,7 @@ func NewUnstarted(client Client) *Exporter { } } -// MarshalLog is the marshaling function used by the logging system to represent this exporter. +// MarshalLog is the marshaling function used by the logging system to represent this Exporter. func (e *Exporter) MarshalLog() interface{} { return struct { Type string diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/doc.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/doc.go index 1f514ef9e..a3c2690c5 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/doc.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/doc.go @@ -28,7 +28,7 @@ The value may additionally a port, a scheme, and a path. The value accepts "http" and "https" scheme. The value should not contain a query string or fragment. OTEL_EXPORTER_OTLP_TRACES_ENDPOINT takes precedence over OTEL_EXPORTER_OTLP_ENDPOINT. -The configuration can be overridden by [WithEndpoint], [WithInsecure], [WithGRPCConn] options. +The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WithInsecure], and [WithGRPCConn] options. OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_TRACES_INSECURE (default: "false") - setting "true" disables client transport security for the exporter's gRPC connection. diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go index dddb1f334..f0203cbe7 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go @@ -20,6 +20,7 @@ package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ import ( "crypto/tls" "fmt" + "net/url" "path" "strings" "time" @@ -32,6 +33,7 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry" + "go.opentelemetry.io/otel/internal/global" ) const ( @@ -265,6 +267,24 @@ func WithEndpoint(endpoint string) GenericOption { }) } +func WithEndpointURL(v string) GenericOption { + return newGenericOption(func(cfg Config) Config { + u, err := url.Parse(v) + if err != nil { + global.Error(err, "otlptrace: parse endpoint url", "url", v) + return cfg + } + + cfg.Traces.Endpoint = u.Host + cfg.Traces.URLPath = u.Path + if u.Scheme != "https" { + cfg.Traces.Insecure = true + } + + return cfg + }) +} + func WithCompression(compression Compression) GenericOption { return newGenericOption(func(cfg Config) Config { cfg.Traces.Compression = compression diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/options.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/options.go index 17ffeaf6e..0fddac758 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/options.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/options.go @@ -64,14 +64,44 @@ func WithInsecure() Option { return wrappedOption{otlpconfig.WithInsecure()} } -// WithEndpoint sets the target endpoint the exporter will connect to. If -// unset, localhost:4317 will be used as a default. +// WithEndpointURL sets the target endpoint URL the Exporter will connect to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT +// will take precedence. +// +// If both this option and WithEndpointURL are used, the last used option will +// take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4317" will be used. // // This option has no effect if WithGRPCConn is used. func WithEndpoint(endpoint string) Option { return wrappedOption{otlpconfig.WithEndpoint(endpoint)} } +// WithEndpoint sets the target endpoint URL the Exporter will connect to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT +// will take precedence. +// +// If both this option and WithEndpoint are used, the last used option will +// take precedence. +// +// If an invalid URL is provided, the default value will be kept. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4317" will be used. +// +// This option has no effect if WithGRPCConn is used. +func WithEndpointURL(u string) Option { + return wrappedOption{otlpconfig.WithEndpointURL(u)} +} + // WithReconnectionPeriod set the minimum amount of time between connection // attempts to the target endpoint. // diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go index 3b3bc3591..54cda3a16 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go @@ -16,5 +16,5 @@ package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace" // Version is the current release version of the OpenTelemetry OTLP trace exporter in use. func Version() string { - return "1.22.0" + return "1.23.1" } diff --git a/vendor/go.opentelemetry.io/otel/sdk/resource/auto.go b/vendor/go.opentelemetry.io/otel/sdk/resource/auto.go index 4279013be..aed756c5e 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/resource/auto.go +++ b/vendor/go.opentelemetry.io/otel/sdk/resource/auto.go @@ -41,8 +41,20 @@ type Detector interface { // must never be done outside of a new major release. } -// Detect calls all input detectors sequentially and merges each result with the previous one. -// It returns the merged error too. +// Detect returns a new [Resource] merged from all the Resources each of the +// detectors produces. Each of the detectors are called sequentially, in the +// order they are passed, merging the produced resource into the previous. +// +// This may return a partial Resource along with an error containing +// [ErrPartialResource] if that error is returned from a detector. It may also +// return a merge-conflicting Resource along with an error containing +// [ErrSchemaURLConflict] if merging Resources from different detectors results +// in a schema URL conflict. It is up to the caller to determine if this +// returned Resource should be used or not. +// +// If one of the detectors returns an error that is not [ErrPartialResource], +// the resource produced by the detector will not be merged and the returned +// error will wrap that detector's error. func Detect(ctx context.Context, detectors ...Detector) (*Resource, error) { r := new(Resource) return r, detect(ctx, r, detectors) @@ -50,6 +62,10 @@ func Detect(ctx context.Context, detectors ...Detector) (*Resource, error) { // detect runs all detectors using ctx and merges the result into res. This // assumes res is allocated and not nil, it will panic otherwise. +// +// If the detectors or merging resources produces any errors (i.e. +// [ErrPartialResource] [ErrSchemaURLConflict]), a single error wrapping all of +// these errors will be returned. Otherwise, nil is returned. func detect(ctx context.Context, res *Resource, detectors []Detector) error { var ( r *Resource @@ -78,6 +94,11 @@ func detect(ctx context.Context, res *Resource, detectors []Detector) error { if len(errs) == 0 { return nil } + if errors.Is(errs, ErrSchemaURLConflict) { + // If there has been a merge conflict, ensure the resource has no + // schema URL. + res.schemaURL = "" + } return errs } diff --git a/vendor/go.opentelemetry.io/otel/sdk/resource/container.go b/vendor/go.opentelemetry.io/otel/sdk/resource/container.go index c881b2895..c1b47193f 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/resource/container.go +++ b/vendor/go.opentelemetry.io/otel/sdk/resource/container.go @@ -29,7 +29,7 @@ type containerIDProvider func() (string, error) var ( containerID containerIDProvider = getContainerIDFromCGroup - cgroupContainerIDRe = regexp.MustCompile(`^.*/(?:.*-)?([0-9a-f]+)(?:\.|\s*$)`) + cgroupContainerIDRe = regexp.MustCompile(`^.*/(?:.*[-:])?([0-9a-f]+)(?:\.|\s*$)`) ) type cgroupContainerIDDetector struct{} diff --git a/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go b/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go index 176ff1066..cb1ee0a9c 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go +++ b/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go @@ -17,6 +17,7 @@ package resource // import "go.opentelemetry.io/otel/sdk/resource" import ( "context" "errors" + "fmt" "sync" "go.opentelemetry.io/otel" @@ -40,9 +41,20 @@ var ( defaultResourceOnce sync.Once ) -var errMergeConflictSchemaURL = errors.New("cannot merge resource due to conflicting Schema URL") +// ErrSchemaURLConflict is an error returned when two Resources are merged +// together that contain different, non-empty, schema URLs. +var ErrSchemaURLConflict = errors.New("conflicting Schema URL") -// New returns a Resource combined from the user-provided detectors. +// New returns a [Resource] built using opts. +// +// This may return a partial Resource along with an error containing +// [ErrPartialResource] if options that provide a [Detector] are used and that +// error is returned from one or more of the Detectors. It may also return a +// merge-conflict Resource along with an error containing +// [ErrSchemaURLConflict] if merging Resources from the opts results in a +// schema URL conflict (see [Resource.Merge] for more information). It is up to +// the caller to determine if this returned Resource should be used or not +// based on these errors. func New(ctx context.Context, opts ...Option) (*Resource, error) { cfg := config{} for _, opt := range opts { @@ -98,7 +110,7 @@ func (r *Resource) String() string { return r.attrs.Encoded(attribute.DefaultEncoder()) } -// MarshalLog is the marshaling function used by the logging system to represent this exporter. +// MarshalLog is the marshaling function used by the logging system to represent this Resource. func (r *Resource) MarshalLog() interface{} { return struct { Attributes attribute.Set @@ -146,16 +158,29 @@ func (r *Resource) Equal(eq *Resource) bool { return r.Equivalent() == eq.Equivalent() } -// Merge creates a new resource by combining resource a and b. +// Merge creates a new [Resource] by merging a and b. // -// If there are common keys between resource a and b, then the value -// from resource b will overwrite the value from resource a, even -// if resource b's value is empty. +// If there are common keys between a and b, then the value from b will +// overwrite the value from a, even if b's value is empty. // -// The SchemaURL of the resources will be merged according to the spec rules: -// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/resource/sdk.md#merge -// If the resources have different non-empty schemaURL an empty resource and an error -// will be returned. +// The SchemaURL of the resources will be merged according to the +// [OpenTelemetry specification rules]: +// +// - If a's schema URL is empty then the returned Resource's schema URL will +// be set to the schema URL of b, +// - Else if b's schema URL is empty then the returned Resource's schema URL +// will be set to the schema URL of a, +// - Else if the schema URLs of a and b are the same then that will be the +// schema URL of the returned Resource, +// - Else this is a merging error. If the resources have different, +// non-empty, schema URLs an error containing [ErrSchemaURLConflict] will +// be returned with the merged Resource. The merged Resource will have an +// empty schema URL. It may be the case that some unintended attributes +// have been overwritten or old semantic conventions persisted in the +// returned Resource. It is up to the caller to determine if this returned +// Resource should be used or not. +// +// [OpenTelemetry specification rules]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/resource/sdk.md#merge func Merge(a, b *Resource) (*Resource, error) { if a == nil && b == nil { return Empty(), nil @@ -167,19 +192,6 @@ func Merge(a, b *Resource) (*Resource, error) { return a, nil } - // Merge the schema URL. - var schemaURL string - switch true { - case a.schemaURL == "": - schemaURL = b.schemaURL - case b.schemaURL == "": - schemaURL = a.schemaURL - case a.schemaURL == b.schemaURL: - schemaURL = a.schemaURL - default: - return Empty(), errMergeConflictSchemaURL - } - // Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key() // Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...) mi := attribute.NewMergeIterator(b.Set(), a.Set()) @@ -187,8 +199,23 @@ func Merge(a, b *Resource) (*Resource, error) { for mi.Next() { combine = append(combine, mi.Attribute()) } - merged := NewWithAttributes(schemaURL, combine...) - return merged, nil + + switch { + case a.schemaURL == "": + return NewWithAttributes(b.schemaURL, combine...), nil + case b.schemaURL == "": + return NewWithAttributes(a.schemaURL, combine...), nil + case a.schemaURL == b.schemaURL: + return NewWithAttributes(a.schemaURL, combine...), nil + } + // Return the merged resource with an appropriate error. It is up to + // the user to decide if the returned resource can be used or not. + return NewSchemaless(combine...), fmt.Errorf( + "%w: %s and %s", + ErrSchemaURLConflict, + a.schemaURL, + b.schemaURL, + ) } // Empty returns an instance of Resource with no attributes. It is diff --git a/vendor/go.opentelemetry.io/otel/sdk/trace/batch_span_processor.go b/vendor/go.opentelemetry.io/otel/sdk/trace/batch_span_processor.go index c9c7effbf..fca26f2e7 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/trace/batch_span_processor.go +++ b/vendor/go.opentelemetry.io/otel/sdk/trace/batch_span_processor.go @@ -406,7 +406,7 @@ func (bsp *batchSpanProcessor) enqueueDrop(ctx context.Context, sd ReadOnlySpan) return false } -// MarshalLog is the marshaling function used by the logging system to represent this exporter. +// MarshalLog is the marshaling function used by the logging system to represent this Span Processor. func (bsp *batchSpanProcessor) MarshalLog() interface{} { return struct { Type string diff --git a/vendor/go.opentelemetry.io/otel/sdk/trace/provider.go b/vendor/go.opentelemetry.io/otel/sdk/trace/provider.go index 7d46c4b48..b1ac60846 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/trace/provider.go +++ b/vendor/go.opentelemetry.io/otel/sdk/trace/provider.go @@ -55,7 +55,7 @@ type tracerProviderConfig struct { resource *resource.Resource } -// MarshalLog is the marshaling function used by the logging system to represent this exporter. +// MarshalLog is the marshaling function used by the logging system to represent this Provider. func (cfg tracerProviderConfig) MarshalLog() interface{} { return struct { SpanProcessors []SpanProcessor diff --git a/vendor/go.opentelemetry.io/otel/sdk/version.go b/vendor/go.opentelemetry.io/otel/sdk/version.go index 17b16cc79..b6910a011 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/version.go +++ b/vendor/go.opentelemetry.io/otel/sdk/version.go @@ -16,5 +16,5 @@ package sdk // import "go.opentelemetry.io/otel/sdk" // Version is the current release version of the OpenTelemetry SDK in use. func Version() string { - return "1.22.0" + return "1.23.1" } diff --git a/vendor/go.opentelemetry.io/otel/version.go b/vendor/go.opentelemetry.io/otel/version.go index 38ba951ed..0d998bcaf 100644 --- a/vendor/go.opentelemetry.io/otel/version.go +++ b/vendor/go.opentelemetry.io/otel/version.go @@ -16,5 +16,5 @@ package otel // import "go.opentelemetry.io/otel" // Version is the current release version of OpenTelemetry in use. func Version() string { - return "1.23.0" + return "1.23.1" } diff --git a/vendor/go.opentelemetry.io/otel/versions.yaml b/vendor/go.opentelemetry.io/otel/versions.yaml index 028393f07..19a73c626 100644 --- a/vendor/go.opentelemetry.io/otel/versions.yaml +++ b/vendor/go.opentelemetry.io/otel/versions.yaml @@ -14,7 +14,7 @@ module-sets: stable-v1: - version: v1.23.0 + version: v1.23.1 modules: - go.opentelemetry.io/otel - go.opentelemetry.io/otel/bridge/opencensus @@ -40,7 +40,7 @@ module-sets: - go.opentelemetry.io/otel/sdk/metric - go.opentelemetry.io/otel/trace experimental-metrics: - version: v0.45.1 + version: v0.45.2 modules: - go.opentelemetry.io/otel/example/prometheus - go.opentelemetry.io/otel/exporters/prometheus diff --git a/vendor/go.opentelemetry.io/proto/otlp/trace/v1/trace.pb.go b/vendor/go.opentelemetry.io/proto/otlp/trace/v1/trace.pb.go index 51a499816..710908837 100644 --- a/vendor/go.opentelemetry.io/proto/otlp/trace/v1/trace.pb.go +++ b/vendor/go.opentelemetry.io/proto/otlp/trace/v1/trace.pb.go @@ -36,6 +36,69 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// SpanFlags represents constants used to interpret the +// Span.flags field, which is protobuf 'fixed32' type and is to +// be used as bit-fields. Each non-zero value defined in this enum is +// a bit-mask. To extract the bit-field, for example, use an +// expression like: +// +// (span.flags & SPAN_FLAGS_TRACE_FLAGS_MASK) +// +// See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. +// +// Note that Span flags were introduced in version 1.1 of the +// OpenTelemetry protocol. Older Span producers do not set this +// field, consequently consumers should not rely on the absence of a +// particular flag bit to indicate the presence of a particular feature. +type SpanFlags int32 + +const ( + // The zero value for the enum. Should not be used for comparisons. + // Instead use bitwise "and" with the appropriate mask as shown above. + SpanFlags_SPAN_FLAGS_DO_NOT_USE SpanFlags = 0 + // Bits 0-7 are used for trace flags. + SpanFlags_SPAN_FLAGS_TRACE_FLAGS_MASK SpanFlags = 255 +) + +// Enum value maps for SpanFlags. +var ( + SpanFlags_name = map[int32]string{ + 0: "SPAN_FLAGS_DO_NOT_USE", + 255: "SPAN_FLAGS_TRACE_FLAGS_MASK", + } + SpanFlags_value = map[string]int32{ + "SPAN_FLAGS_DO_NOT_USE": 0, + "SPAN_FLAGS_TRACE_FLAGS_MASK": 255, + } +) + +func (x SpanFlags) Enum() *SpanFlags { + p := new(SpanFlags) + *p = x + return p +} + +func (x SpanFlags) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SpanFlags) Descriptor() protoreflect.EnumDescriptor { + return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[0].Descriptor() +} + +func (SpanFlags) Type() protoreflect.EnumType { + return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[0] +} + +func (x SpanFlags) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SpanFlags.Descriptor instead. +func (SpanFlags) EnumDescriptor() ([]byte, []int) { + return file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP(), []int{0} +} + // SpanKind is the type of span. Can be used to specify additional relationships between spans // in addition to a parent/child relationship. type Span_SpanKind int32 @@ -94,11 +157,11 @@ func (x Span_SpanKind) String() string { } func (Span_SpanKind) Descriptor() protoreflect.EnumDescriptor { - return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[0].Descriptor() + return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[1].Descriptor() } func (Span_SpanKind) Type() protoreflect.EnumType { - return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[0] + return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[1] } func (x Span_SpanKind) Number() protoreflect.EnumNumber { @@ -149,11 +212,11 @@ func (x Status_StatusCode) String() string { } func (Status_StatusCode) Descriptor() protoreflect.EnumDescriptor { - return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[1].Descriptor() + return file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[2].Descriptor() } func (Status_StatusCode) Type() protoreflect.EnumType { - return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[1] + return &file_opentelemetry_proto_trace_v1_trace_proto_enumTypes[2] } func (x Status_StatusCode) Number() protoreflect.EnumNumber { @@ -238,6 +301,9 @@ type ResourceSpans struct { Resource *v1.Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` // A list of ScopeSpans that originate from a resource. ScopeSpans []*ScopeSpans `protobuf:"bytes,2,rep,name=scope_spans,json=scopeSpans,proto3" json:"scope_spans,omitempty"` + // The Schema URL, if known. This is the identifier of the Schema that the resource data + // is recorded in. To learn more about Schema URL see + // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url // This schema_url applies to the data in the "resource" field. It does not apply // to the data in the "scope_spans" field which have their own schema_url field. SchemaUrl string `protobuf:"bytes,3,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"` @@ -308,6 +374,9 @@ type ScopeSpans struct { Scope *v11.InstrumentationScope `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"` // A list of Spans that originate from an instrumentation scope. Spans []*Span `protobuf:"bytes,2,rep,name=spans,proto3" json:"spans,omitempty"` + // The Schema URL, if known. This is the identifier of the Schema that the span data + // is recorded in. To learn more about Schema URL see + // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url // This schema_url applies to all spans and span events in the "spans" field. SchemaUrl string `protobuf:"bytes,3,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"` } @@ -394,6 +463,21 @@ type Span struct { // The `span_id` of this span's parent span. If this is a root span, then this // field must be empty. The ID is an 8-byte array. ParentSpanId []byte `protobuf:"bytes,4,opt,name=parent_span_id,json=parentSpanId,proto3" json:"parent_span_id,omitempty"` + // Flags, a bit field. 8 least significant bits are the trace + // flags as defined in W3C Trace Context specification. Readers + // MUST not assume that 24 most significant bits will be zero. + // To read the 8-bit W3C trace flag, use `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`. + // + // When creating span messages, if the message is logically forwarded from another source + // with an equivalent flags fields (i.e., usually another OTLP span message), the field SHOULD + // be copied as-is. If creating from a source that does not have an equivalent flags field + // (such as a runtime representation of an OpenTelemetry span), the high 24 bits MUST + // be set to zero. + // + // [Optional]. + // + // See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. + Flags uint32 `protobuf:"fixed32,16,opt,name=flags,proto3" json:"flags,omitempty"` // A description of the span's operation. // // For example, the name can be a qualified method name or a file name @@ -517,6 +601,13 @@ func (x *Span) GetParentSpanId() []byte { return nil } +func (x *Span) GetFlags() uint32 { + if x != nil { + return x.Flags + } + return 0 +} + func (x *Span) GetName() string { if x != nil { return x.Name @@ -757,6 +848,15 @@ type Span_Link struct { // dropped_attributes_count is the number of dropped attributes. If the value is 0, // then no attributes were dropped. DroppedAttributesCount uint32 `protobuf:"varint,5,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` + // Flags, a bit field. 8 least significant bits are the trace + // flags as defined in W3C Trace Context specification. Readers + // MUST not assume that 24 most significant bits will be zero. + // When creating new spans, the most-significant 24-bits MUST be + // zero. To read the 8-bit W3C trace flag (use flags & + // SPAN_FLAGS_TRACE_FLAGS_MASK). [Optional]. + // + // See https://www.w3.org/TR/trace-context-2/#trace-flags for the flag definitions. + Flags uint32 `protobuf:"fixed32,6,opt,name=flags,proto3" json:"flags,omitempty"` } func (x *Span_Link) Reset() { @@ -826,6 +926,13 @@ func (x *Span_Link) GetDroppedAttributesCount() uint32 { return 0 } +func (x *Span_Link) GetFlags() uint32 { + if x != nil { + return x.Flags + } + return 0 +} + var File_opentelemetry_proto_trace_v1_trace_proto protoreflect.FileDescriptor var file_opentelemetry_proto_trace_v1_trace_proto_rawDesc = []byte{ @@ -869,7 +976,7 @@ var file_opentelemetry_proto_trace_v1_trace_proto_rawDesc = []byte{ 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x05, 0x73, 0x70, 0x61, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, - 0x61, 0x55, 0x72, 0x6c, 0x22, 0x9c, 0x0a, 0x0a, 0x04, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x19, 0x0a, + 0x61, 0x55, 0x72, 0x6c, 0x22, 0xc8, 0x0a, 0x0a, 0x04, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49, @@ -877,101 +984,108 @@ var file_opentelemetry_proto_trace_v1_trace_proto_rawDesc = []byte{ 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x65, - 0x6e, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x04, - 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x6f, 0x70, 0x65, - 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x53, - 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x2f, 0x0a, - 0x14, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, - 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x06, 0x52, 0x11, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x2b, - 0x0a, 0x12, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, - 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0f, 0x65, 0x6e, 0x64, 0x54, - 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x47, 0x0a, 0x0a, 0x61, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, - 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x40, - 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, - 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, - 0x61, 0x6e, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, - 0x12, 0x30, 0x0a, 0x14, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, - 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, - 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x6b, - 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6e, - 0x6b, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, - 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x4c, 0x69, 0x6e, 0x6b, 0x73, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, - 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, - 0xc4, 0x01, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x69, 0x6d, - 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x06, 0x52, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, - 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, - 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, - 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, - 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, - 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0xde, 0x01, 0x0a, 0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x12, - 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x70, - 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x70, 0x61, - 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, - 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, - 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x99, 0x01, 0x0a, 0x08, 0x53, 0x70, 0x61, 0x6e, - 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, - 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x54, - 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x50, 0x41, 0x4e, 0x5f, - 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x12, 0x14, 0x0a, - 0x10, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, - 0x54, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, - 0x5f, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x45, 0x52, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x53, - 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x53, 0x55, 0x4d, 0x45, - 0x52, 0x10, 0x05, 0x22, 0xbd, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, - 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x43, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, - 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, - 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x4e, 0x0a, - 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, - 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54, - 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44, - 0x45, 0x5f, 0x4f, 0x4b, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, - 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x4a, 0x04, 0x08, - 0x01, 0x10, 0x02, 0x42, 0x77, 0x0a, 0x1f, 0x69, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, + 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x07, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x2b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, + 0x69, 0x6e, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x06, 0x52, 0x11, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, + 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x2b, 0x0a, 0x12, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x06, + 0x52, 0x0f, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, + 0x6f, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, + 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, + 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, + 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, + 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0b, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, + 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b, + 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, - 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x72, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x27, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, - 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x6f, 0x74, 0x6c, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x76, 0x31, 0xaa, 0x02, 0x1c, - 0x4f, 0x70, 0x65, 0x6e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, + 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x72, 0x6f, 0x70, 0x70, + 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x4c, 0x69, 0x6e, + 0x6b, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, + 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0xc4, 0x01, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x24, 0x0a, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, + 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, + 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, + 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0xf4, 0x01, 0x0a, + 0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x17, 0x0a, 0x07, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x06, 0x73, 0x70, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x61, + 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, + 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x07, 0x52, 0x05, 0x66, 0x6c, + 0x61, 0x67, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x08, 0x53, 0x70, 0x61, 0x6e, 0x4b, 0x69, 0x6e, 0x64, + 0x12, 0x19, 0x0a, 0x15, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x53, + 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, + 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, + 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x50, 0x41, + 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x03, 0x12, + 0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x50, 0x52, 0x4f, + 0x44, 0x55, 0x43, 0x45, 0x52, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x50, 0x41, 0x4e, 0x5f, + 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x53, 0x55, 0x4d, 0x45, 0x52, 0x10, 0x05, 0x22, + 0xbd, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x43, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, + 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x4e, 0x0a, 0x0a, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, 0x00, 0x12, 0x12, + 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x4f, 0x4b, + 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x4f, 0x44, + 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x2a, + 0x48, 0x0a, 0x09, 0x53, 0x70, 0x61, 0x6e, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x19, 0x0a, 0x15, + 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x46, 0x4c, 0x41, 0x47, 0x53, 0x5f, 0x44, 0x4f, 0x5f, 0x4e, 0x4f, + 0x54, 0x5f, 0x55, 0x53, 0x45, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1b, 0x53, 0x50, 0x41, 0x4e, 0x5f, + 0x46, 0x4c, 0x41, 0x47, 0x53, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x46, 0x4c, 0x41, 0x47, + 0x53, 0x5f, 0x4d, 0x41, 0x53, 0x4b, 0x10, 0xff, 0x01, 0x42, 0x77, 0x0a, 0x1f, 0x69, 0x6f, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, 0x72, + 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x27, 0x67, 0x6f, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x69, 0x6f, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, 0x74, 0x6c, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, + 0x2f, 0x76, 0x31, 0xaa, 0x02, 0x1c, 0x4f, 0x70, 0x65, 0x6e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x2e, + 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -986,36 +1100,37 @@ func file_opentelemetry_proto_trace_v1_trace_proto_rawDescGZIP() []byte { return file_opentelemetry_proto_trace_v1_trace_proto_rawDescData } -var file_opentelemetry_proto_trace_v1_trace_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_opentelemetry_proto_trace_v1_trace_proto_enumTypes = make([]protoimpl.EnumInfo, 3) var file_opentelemetry_proto_trace_v1_trace_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_opentelemetry_proto_trace_v1_trace_proto_goTypes = []interface{}{ - (Span_SpanKind)(0), // 0: opentelemetry.proto.trace.v1.Span.SpanKind - (Status_StatusCode)(0), // 1: opentelemetry.proto.trace.v1.Status.StatusCode - (*TracesData)(nil), // 2: opentelemetry.proto.trace.v1.TracesData - (*ResourceSpans)(nil), // 3: opentelemetry.proto.trace.v1.ResourceSpans - (*ScopeSpans)(nil), // 4: opentelemetry.proto.trace.v1.ScopeSpans - (*Span)(nil), // 5: opentelemetry.proto.trace.v1.Span - (*Status)(nil), // 6: opentelemetry.proto.trace.v1.Status - (*Span_Event)(nil), // 7: opentelemetry.proto.trace.v1.Span.Event - (*Span_Link)(nil), // 8: opentelemetry.proto.trace.v1.Span.Link - (*v1.Resource)(nil), // 9: opentelemetry.proto.resource.v1.Resource - (*v11.InstrumentationScope)(nil), // 10: opentelemetry.proto.common.v1.InstrumentationScope - (*v11.KeyValue)(nil), // 11: opentelemetry.proto.common.v1.KeyValue + (SpanFlags)(0), // 0: opentelemetry.proto.trace.v1.SpanFlags + (Span_SpanKind)(0), // 1: opentelemetry.proto.trace.v1.Span.SpanKind + (Status_StatusCode)(0), // 2: opentelemetry.proto.trace.v1.Status.StatusCode + (*TracesData)(nil), // 3: opentelemetry.proto.trace.v1.TracesData + (*ResourceSpans)(nil), // 4: opentelemetry.proto.trace.v1.ResourceSpans + (*ScopeSpans)(nil), // 5: opentelemetry.proto.trace.v1.ScopeSpans + (*Span)(nil), // 6: opentelemetry.proto.trace.v1.Span + (*Status)(nil), // 7: opentelemetry.proto.trace.v1.Status + (*Span_Event)(nil), // 8: opentelemetry.proto.trace.v1.Span.Event + (*Span_Link)(nil), // 9: opentelemetry.proto.trace.v1.Span.Link + (*v1.Resource)(nil), // 10: opentelemetry.proto.resource.v1.Resource + (*v11.InstrumentationScope)(nil), // 11: opentelemetry.proto.common.v1.InstrumentationScope + (*v11.KeyValue)(nil), // 12: opentelemetry.proto.common.v1.KeyValue } var file_opentelemetry_proto_trace_v1_trace_proto_depIdxs = []int32{ - 3, // 0: opentelemetry.proto.trace.v1.TracesData.resource_spans:type_name -> opentelemetry.proto.trace.v1.ResourceSpans - 9, // 1: opentelemetry.proto.trace.v1.ResourceSpans.resource:type_name -> opentelemetry.proto.resource.v1.Resource - 4, // 2: opentelemetry.proto.trace.v1.ResourceSpans.scope_spans:type_name -> opentelemetry.proto.trace.v1.ScopeSpans - 10, // 3: opentelemetry.proto.trace.v1.ScopeSpans.scope:type_name -> opentelemetry.proto.common.v1.InstrumentationScope - 5, // 4: opentelemetry.proto.trace.v1.ScopeSpans.spans:type_name -> opentelemetry.proto.trace.v1.Span - 0, // 5: opentelemetry.proto.trace.v1.Span.kind:type_name -> opentelemetry.proto.trace.v1.Span.SpanKind - 11, // 6: opentelemetry.proto.trace.v1.Span.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue - 7, // 7: opentelemetry.proto.trace.v1.Span.events:type_name -> opentelemetry.proto.trace.v1.Span.Event - 8, // 8: opentelemetry.proto.trace.v1.Span.links:type_name -> opentelemetry.proto.trace.v1.Span.Link - 6, // 9: opentelemetry.proto.trace.v1.Span.status:type_name -> opentelemetry.proto.trace.v1.Status - 1, // 10: opentelemetry.proto.trace.v1.Status.code:type_name -> opentelemetry.proto.trace.v1.Status.StatusCode - 11, // 11: opentelemetry.proto.trace.v1.Span.Event.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue - 11, // 12: opentelemetry.proto.trace.v1.Span.Link.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue + 4, // 0: opentelemetry.proto.trace.v1.TracesData.resource_spans:type_name -> opentelemetry.proto.trace.v1.ResourceSpans + 10, // 1: opentelemetry.proto.trace.v1.ResourceSpans.resource:type_name -> opentelemetry.proto.resource.v1.Resource + 5, // 2: opentelemetry.proto.trace.v1.ResourceSpans.scope_spans:type_name -> opentelemetry.proto.trace.v1.ScopeSpans + 11, // 3: opentelemetry.proto.trace.v1.ScopeSpans.scope:type_name -> opentelemetry.proto.common.v1.InstrumentationScope + 6, // 4: opentelemetry.proto.trace.v1.ScopeSpans.spans:type_name -> opentelemetry.proto.trace.v1.Span + 1, // 5: opentelemetry.proto.trace.v1.Span.kind:type_name -> opentelemetry.proto.trace.v1.Span.SpanKind + 12, // 6: opentelemetry.proto.trace.v1.Span.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue + 8, // 7: opentelemetry.proto.trace.v1.Span.events:type_name -> opentelemetry.proto.trace.v1.Span.Event + 9, // 8: opentelemetry.proto.trace.v1.Span.links:type_name -> opentelemetry.proto.trace.v1.Span.Link + 7, // 9: opentelemetry.proto.trace.v1.Span.status:type_name -> opentelemetry.proto.trace.v1.Status + 2, // 10: opentelemetry.proto.trace.v1.Status.code:type_name -> opentelemetry.proto.trace.v1.Status.StatusCode + 12, // 11: opentelemetry.proto.trace.v1.Span.Event.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue + 12, // 12: opentelemetry.proto.trace.v1.Span.Link.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue 13, // [13:13] is the sub-list for method output_type 13, // [13:13] is the sub-list for method input_type 13, // [13:13] is the sub-list for extension type_name @@ -1119,7 +1234,7 @@ func file_opentelemetry_proto_trace_v1_trace_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_opentelemetry_proto_trace_v1_trace_proto_rawDesc, - NumEnums: 2, + NumEnums: 3, NumMessages: 7, NumExtensions: 0, NumServices: 0, diff --git a/vendor/modules.txt b/vendor/modules.txt index d3129c6cb..e27a3b9cf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1942,7 +1942,7 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvut ## explicit; go 1.20 go.opentelemetry.io/contrib/zpages go.opentelemetry.io/contrib/zpages/internal -# go.opentelemetry.io/otel v1.23.0 +# go.opentelemetry.io/otel v1.23.1 ## explicit; go 1.20 go.opentelemetry.io/otel go.opentelemetry.io/otel/attribute @@ -1967,22 +1967,22 @@ go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/agent go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/jaeger go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/zipkincore go.opentelemetry.io/otel/exporters/jaeger/internal/third_party/thrift/lib/go/thrift -# go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 +# go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 ## explicit; go 1.20 go.opentelemetry.io/otel/exporters/otlp/otlptrace go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform -# go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 +# go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 ## explicit; go 1.20 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry -# go.opentelemetry.io/otel/metric v1.23.0 +# go.opentelemetry.io/otel/metric v1.23.1 ## explicit; go 1.20 go.opentelemetry.io/otel/metric go.opentelemetry.io/otel/metric/embedded -# go.opentelemetry.io/otel/sdk v1.22.0 +# go.opentelemetry.io/otel/sdk v1.23.1 ## explicit; go 1.20 go.opentelemetry.io/otel/sdk go.opentelemetry.io/otel/sdk/instrumentation @@ -1990,12 +1990,12 @@ go.opentelemetry.io/otel/sdk/internal go.opentelemetry.io/otel/sdk/internal/env go.opentelemetry.io/otel/sdk/resource go.opentelemetry.io/otel/sdk/trace -# go.opentelemetry.io/otel/trace v1.23.0 +# go.opentelemetry.io/otel/trace v1.23.1 ## explicit; go 1.20 go.opentelemetry.io/otel/trace go.opentelemetry.io/otel/trace/embedded go.opentelemetry.io/otel/trace/noop -# go.opentelemetry.io/proto/otlp v1.0.0 +# go.opentelemetry.io/proto/otlp v1.1.0 ## explicit; go 1.17 go.opentelemetry.io/proto/otlp/collector/trace/v1 go.opentelemetry.io/proto/otlp/common/v1 From d87f5b031634eb60a1033fc6fb57eed732398eb5 Mon Sep 17 00:00:00 2001 From: nirajacharya2 <122071597+nirajacharya2@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:52:22 +0545 Subject: [PATCH 055/112] adding test for updating role of a share (#8329) --- tests/TestHelpers/GraphHelper.php | 2 +- .../features/apiSharingNg/sharedByMe.feature | 71 -------- .../updateShareInvitations.feature | 160 ++++++++++++++++++ .../features/bootstrap/SharingNgContext.php | 69 +++++--- 4 files changed, 207 insertions(+), 95 deletions(-) create mode 100644 tests/acceptance/features/apiSharingNg/updateShareInvitations.feature diff --git a/tests/TestHelpers/GraphHelper.php b/tests/TestHelpers/GraphHelper.php index 8ff0c9c25..b4f5863b4 100644 --- a/tests/TestHelpers/GraphHelper.php +++ b/tests/TestHelpers/GraphHelper.php @@ -1756,7 +1756,7 @@ class GraphHelper { * @return ResponseInterface * @throws GuzzleException */ - public static function updateLinkShare( + public static function updateShare( string $baseUrl, string $xRequestId, string $user, diff --git a/tests/acceptance/features/apiSharingNg/sharedByMe.feature b/tests/acceptance/features/apiSharingNg/sharedByMe.feature index b9f083676..1f30c008f 100644 --- a/tests/acceptance/features/apiSharingNg/sharedByMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedByMe.feature @@ -1451,74 +1451,3 @@ Feature: resources shared by user } } """ - - - Scenario: user updates expiration date of a share - Given user "Alice" has uploaded file with content "hello world" to "testfile.txt" - And user "Alice" has sent the following share invitation: - | resource | testfile.txt | - | space | Personal | - | sharee | Brian | - | shareType | user | - | permissionsRole | Viewer | - When user "Alice" updates the last share with the following using the Graph API: - | space | Personal | - | resource | testfile.txt | - | expirationDateTime | 2200-07-15T14:00:00Z | - Then the HTTP status code should be "200" - And the JSON data of the response should match - """ - { - "type": "object", - "required": [ - "expirationDateTime", - "grantedToV2", - "id", - "roles" - ], - "properties": { - "expirationDateTime": { - "type": "string", - "enum": ["2200-07-15T14:00:00Z"] - }, - "grantedToV2": { - "type": "object", - "required": [ - "user" - ], - "properties":{ - "user": { - "type": "object", - "required": [ - "displayName", - "id" - ], - "properties": { - "displayName": { - "type": "string", - "enum": ["Brian Murphy"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - }, - "id": { - "type": "string", - "pattern": "^%permissions_id_pattern%$" - }, - "roles": { - "type": "array", - "items": [ - { - "type": "string", - "pattern": "^%role_id_pattern%$" - } - ] - } - } - } - """ diff --git a/tests/acceptance/features/apiSharingNg/updateShareInvitations.feature b/tests/acceptance/features/apiSharingNg/updateShareInvitations.feature new file mode 100644 index 000000000..0ba82e457 --- /dev/null +++ b/tests/acceptance/features/apiSharingNg/updateShareInvitations.feature @@ -0,0 +1,160 @@ +Feature: Update permission of a share + As a user + I want to update resources shared with me + So that I can have more control over my shares and manage it + https://owncloud.dev/libre-graph-api/#/drives.permissions/UpdatePermission + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario: user updates expiration date of a share + Given user "Alice" has uploaded file with content "hello world" to "testfile.txt" + And user "Alice" has sent the following share invitation: + | resource | testfile.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + | expirationDateTime | 2025-07-15T14:00:00Z | + When user "Alice" updates the last share with the following using the Graph API: + | space | Personal | + | resource | testfile.txt | + | expirationDateTime | 2200-07-15T14:00:00Z | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "expirationDateTime", + "grantedToV2", + "id", + "roles" + ], + "properties": { + "expirationDateTime": { + "type": "string", + "enum": ["2200-07-15T14:00:00Z"] + }, + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties":{ + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + """ + + + Scenario Outline: user updates role of a share + Given user "Alice" has uploaded file with content "to share" to "/textfile1.txt" + And user "Alice" has created folder "FolderToShare" + And user "Alice" has sent the following share invitation: + | resource | | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + When user "Alice" updates the last share with the following using the Graph API: + | permissionsRole | | + | space | Personal | + | resource | | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties":{ + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + """ + Examples: + | previous-permissions-role | path | new-permissions-role | + | Viewer | textfile1.txt | File Editor | + | File Editor | textfile1.txt | Viewer | + | Viewer | FolderToShare | Uploader | + | Viewer | FolderToShare | Editor | + | Editor | FolderToShare | Viewer | + | Editor | FolderToShare | Uploader | + | Uploader | FolderToShare | Editor | + | Uploader | FolderToShare | Viewer | \ No newline at end of file diff --git a/tests/acceptance/features/bootstrap/SharingNgContext.php b/tests/acceptance/features/bootstrap/SharingNgContext.php index da74b9560..bdd91d4d5 100644 --- a/tests/acceptance/features/bootstrap/SharingNgContext.php +++ b/tests/acceptance/features/bootstrap/SharingNgContext.php @@ -243,7 +243,7 @@ class SharingNgContext implements Context { $response = $this->featureContext->shareNgGetLastCreatedUserGroupShare(); $permissionID = json_decode($response->getBody()->getContents())->value[0]->id; $this->featureContext->setResponse( - $this->updateShare( + $this->updateResourceShare( $user, $table, $permissionID @@ -251,6 +251,41 @@ class SharingNgContext implements Context { ); } + /** + * @param string $user + * @param TableNode $body + * @param string $permissionID + * + * @return ResponseInterface + */ + public function updateResourceShare(string $user, TableNode $body, string $permissionID): ResponseInterface { + $bodyRows = $body->getRowsHash(); + $space = $bodyRows['space']; + $resource = $bodyRows['resource']; + $spaceId = ($this->spacesContext->getSpaceByName($user, $space))["id"]; + $itemId = $this->spacesContext->getResourceId($user, $space, $resource); + $body = []; + + if (\array_key_exists('permissionsRole', $bodyRows)) { + $body["roles"] = [GraphHelper::getPermissionsRoleIdByName($bodyRows['permissionsRole'])]; + } + + if (\array_key_exists('expirationDateTime', $bodyRows)) { + $body["expirationDateTime"] = $bodyRows['expirationDateTime']; + } + + return GraphHelper::updateShare( + $this->featureContext->getBaseUrl(), + $this->featureContext->getStepLineRef(), + $user, + $this->featureContext->getPasswordForUser($user), + $spaceId, + $itemId, + \json_encode($body), + $permissionID + ); + } + /** * @When user :user sends the following share invitation with file-id :fileId using the Graph API: * @@ -308,7 +343,7 @@ class SharingNgContext implements Context { */ public function userUpdatesLastPublicLinkShareUsingTheGraphApiWith(string $user, TableNode $body):void { $this->featureContext->setResponse( - $this->updateShare( + $this->updateLinkShare( $user, $body, $this->featureContext->shareNgGetLastCreatedLinkShareID() @@ -323,35 +358,23 @@ class SharingNgContext implements Context { * * @return ResponseInterface */ - public function updateShare(string $user, TableNode $body, string $permissionID): ResponseInterface { + public function updateLinkShare(string $user, TableNode $body, string $permissionID): ResponseInterface { $bodyRows = $body->getRowsHash(); $space = $bodyRows['space']; $resource = $bodyRows['resource']; $spaceId = ($this->spacesContext->getSpaceByName($user, $space))["id"]; $itemId = $this->spacesContext->getResourceId($user, $space, $resource); + $body = []; - if (\array_key_exists('role', $bodyRows) && \array_key_exists('expirationDateTime', $bodyRows)) { - $body = [ - "expirationDateTime" => $bodyRows['expirationDateTime'], - "link" => [ - "type" => $bodyRows['permissionsRole'] - ] - ]; - } elseif (\array_key_exists('permissionsRole', $bodyRows)) { - $body = [ - "link" => [ - "type" => $bodyRows['permissionsRole'] - ] - ]; - } elseif (\array_key_exists('expirationDateTime', $bodyRows)) { - $body = [ - "expirationDateTime" => $bodyRows['expirationDateTime'] - ]; - } else { - throw new Error('Expiration date or role is missing to update for share link!'); + if (\array_key_exists('permissionsRole', $bodyRows)) { + $body["link"]["type"] = $bodyRows['permissionsRole']; } - return GraphHelper::updateLinkShare( + if (\array_key_exists('expirationDateTime', $bodyRows)) { + $body["expirationDateTime"] = $bodyRows['expirationDateTime']; + } + + return GraphHelper::updateShare( $this->featureContext->getBaseUrl(), $this->featureContext->getStepLineRef(), $user, From 238316c2d866c245baf65d976090b394b506bf6f Mon Sep 17 00:00:00 2001 From: mmattel Date: Thu, 8 Feb 2024 10:19:53 +0100 Subject: [PATCH 056/112] Fix envvar delta for removed envvars and file locations --- Makefile | 3 ++- docs/architecture/_index.md | 2 +- .../_includes/adoc/env-var-deltas/{.keep => .gitkeep} | 0 docs/services/_includes/adoc/env-var-deltas/_index.md | 3 --- .../general-info}/env-var-deltas/4.0.0-5.0.0-added.adoc | 0 .../general-info}/env-var-deltas/4.0.0-5.0.0-added.md | 0 .../general-info}/env-var-deltas/4.0.0-5.0.0-removed.adoc | 8 +++++++- .../general-info}/env-var-deltas/4.0.0-5.0.0-removed.md | 1 + .../env-var-deltas/make-adoc-table-from-md.txt | 0 9 files changed, 11 insertions(+), 6 deletions(-) rename docs/services/_includes/adoc/env-var-deltas/{.keep => .gitkeep} (100%) delete mode 100644 docs/services/_includes/adoc/env-var-deltas/_index.md rename docs/{ => services/general-info}/env-var-deltas/4.0.0-5.0.0-added.adoc (100%) rename docs/{ => services/general-info}/env-var-deltas/4.0.0-5.0.0-added.md (100%) rename docs/{ => services/general-info}/env-var-deltas/4.0.0-5.0.0-removed.adoc (79%) rename docs/{ => services/general-info}/env-var-deltas/4.0.0-5.0.0-removed.md (90%) rename docs/{ => services/general-info}/env-var-deltas/make-adoc-table-from-md.txt (100%) diff --git a/Makefile b/Makefile index 33c3a476c..517f08d4b 100644 --- a/Makefile +++ b/Makefile @@ -172,8 +172,9 @@ docs-generate: @for mod in $(OCIS_MODULES); do \ $(MAKE) --no-print-directory -C $$mod docs-generate || exit 1; \ done + $(MAKE) --no-print-directory -C docs docs-generate || exit 1 - cp docs/env-var-deltas/*.adoc docs/services/_includes/adoc/env-var-deltas/ + cp docs/services/general-info/env-var-deltas/*.adoc docs/services/_includes/adoc/env-var-deltas/ .PHONY: ci-go-generate ci-go-generate: diff --git a/docs/architecture/_index.md b/docs/architecture/_index.md index 968452f16..e4ed80497 100644 --- a/docs/architecture/_index.md +++ b/docs/architecture/_index.md @@ -1,7 +1,7 @@ --- title: Architecture date: 2023-12-06T13:00:00+01:00 -weight: 25 +weight: 10 geekdocRepo: https://github.com/owncloud/ocis geekdocEditPath: edit/master/docs/architecture geekdocFilePath: _index.md diff --git a/docs/services/_includes/adoc/env-var-deltas/.keep b/docs/services/_includes/adoc/env-var-deltas/.gitkeep similarity index 100% rename from docs/services/_includes/adoc/env-var-deltas/.keep rename to docs/services/_includes/adoc/env-var-deltas/.gitkeep diff --git a/docs/services/_includes/adoc/env-var-deltas/_index.md b/docs/services/_includes/adoc/env-var-deltas/_index.md deleted file mode 100644 index b58ce0c82..000000000 --- a/docs/services/_includes/adoc/env-var-deltas/_index.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -GeekdocHidden: true ---- \ No newline at end of file diff --git a/docs/env-var-deltas/4.0.0-5.0.0-added.adoc b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc similarity index 100% rename from docs/env-var-deltas/4.0.0-5.0.0-added.adoc rename to docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc diff --git a/docs/env-var-deltas/4.0.0-5.0.0-added.md b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md similarity index 100% rename from docs/env-var-deltas/4.0.0-5.0.0-added.md rename to docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md diff --git a/docs/env-var-deltas/4.0.0-5.0.0-removed.adoc b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc similarity index 79% rename from docs/env-var-deltas/4.0.0-5.0.0-removed.adoc rename to docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc index 38225205a..8fd35b80d 100644 --- a/docs/env-var-deltas/4.0.0-5.0.0-removed.adoc +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc @@ -28,6 +28,11 @@ | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. +| xref:{s-path}/sharing.adoc[sharing] +| `SHARING_EVENTS_TLS_ROOT_CA_CERT` +| `SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE` +| The root CA certificate used to validate the server's TLS certificate. If provided SHARING_EVENTS_TLS_INSECURE will be seen as false. + | xref:{s-path}/users.adoc[users] | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` @@ -36,5 +41,6 @@ | | `LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` | `OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` -| Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's. +| Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's. + |=== diff --git a/docs/env-var-deltas/4.0.0-5.0.0-removed.md b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.md similarity index 90% rename from docs/env-var-deltas/4.0.0-5.0.0-removed.md rename to docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.md index 2869d71e8..a7dcecb71 100644 --- a/docs/env-var-deltas/4.0.0-5.0.0-removed.md +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.md @@ -6,5 +6,6 @@ | services/graph/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | | services/groups/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | | services/idp/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | +| services/sharing/pkg/config/config.go | `SHARING_EVENTS_TLS_ROOT_CA_CERT` | `SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE` | The root CA certificate used to validate the server's TLS certificate. If provided SHARING_EVENTS_TLS_INSECURE will be seen as false. | | services/users/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | | services/users/pkg/config/config.go | `LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` | `OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` | Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's. | diff --git a/docs/env-var-deltas/make-adoc-table-from-md.txt b/docs/services/general-info/env-var-deltas/make-adoc-table-from-md.txt similarity index 100% rename from docs/env-var-deltas/make-adoc-table-from-md.txt rename to docs/services/general-info/env-var-deltas/make-adoc-table-from-md.txt From 94e86eb8dea57aa279b09d7e24f2bc282099f9cb Mon Sep 17 00:00:00 2001 From: Roman Perekhod Date: Thu, 8 Feb 2024 12:01:55 +0100 Subject: [PATCH 057/112] fix an error when move using destination id --- changelog/unreleased/fix-move.md | 7 +++++++ go.mod | 2 +- go.sum | 4 ++-- .../reva/v2/internal/http/services/owncloud/ocdav/move.go | 6 +++++- vendor/modules.txt | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 changelog/unreleased/fix-move.md diff --git a/changelog/unreleased/fix-move.md b/changelog/unreleased/fix-move.md new file mode 100644 index 000000000..aabda3a1c --- /dev/null +++ b/changelog/unreleased/fix-move.md @@ -0,0 +1,7 @@ +Bugfix: Fix an error when move + +We fixed a bug that caused Internal Server Error when move using destination id + +https://github.com/owncloud/ocis/pull/8396 +https://github.com/cs3org/reva/pull/4503 +https://github.com/owncloud/ocis/issues/6739 diff --git a/go.mod b/go.mod index b0952cbaa..b743daa10 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/blevesearch/bleve/v2 v2.3.10 github.com/coreos/go-oidc/v3 v3.9.0 github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 - github.com/cs3org/reva/v2 v2.18.1-0.20240206135152-2343fe56d568 + github.com/cs3org/reva/v2 v2.18.1-0.20240208105019-d5e420d294be github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 github.com/disintegration/imaging v1.6.2 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e diff --git a/go.sum b/go.sum index ce4c381a5..e49f1ed17 100644 --- a/go.sum +++ b/go.sum @@ -1019,8 +1019,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= -github.com/cs3org/reva/v2 v2.18.1-0.20240206135152-2343fe56d568 h1:RDytVxpVjcJNLQM34yFvHkxnysVQJ0Sa4k4vuI1SH5o= -github.com/cs3org/reva/v2 v2.18.1-0.20240206135152-2343fe56d568/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= +github.com/cs3org/reva/v2 v2.18.1-0.20240208105019-d5e420d294be h1:vljLn7q9NPfiynhzGBRD1sv8qhYGd6SZOHgvzv+Q9iA= +github.com/cs3org/reva/v2 v2.18.1-0.20240208105019-d5e420d294be/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/move.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/move.go index fbd51ee72..4706d20e9 100644 --- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/move.go +++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/move.go @@ -266,7 +266,11 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req } // TODO what if intermediate is a file? } - + // resolve the destination path + if dst.Path == "." { + dst.Path = utils.MakeRelativePath(dstStatRes.GetInfo().GetName()) + dst.ResourceId = dstStatRes.GetInfo().GetParentId() + } mReq := &provider.MoveRequest{Source: src, Destination: dst} mRes, err := client.Move(ctx, mReq) if err != nil { diff --git a/vendor/modules.txt b/vendor/modules.txt index d3129c6cb..dd305259f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -362,7 +362,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1 github.com/cs3org/go-cs3apis/cs3/types/v1beta1 -# github.com/cs3org/reva/v2 v2.18.1-0.20240206135152-2343fe56d568 +# github.com/cs3org/reva/v2 v2.18.1-0.20240208105019-d5e420d294be ## explicit; go 1.21 github.com/cs3org/reva/v2/cmd/revad/internal/grace github.com/cs3org/reva/v2/cmd/revad/runtime From 3628f1502f678a4fc797e2737a0e2a262a31074d Mon Sep 17 00:00:00 2001 From: mmattel Date: Thu, 8 Feb 2024 14:23:19 +0100 Subject: [PATCH 058/112] [docs-only] Add missing added envvar to list --- .../general-info/env-var-deltas/4.0.0-5.0.0-added.adoc | 3 +++ docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md | 1 + .../general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc index 7498bf465..6edb3679c 100644 --- a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc @@ -18,6 +18,9 @@ | | `OCIS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| +| +| `ANTIVIRUS_ICAP_TIMEOUT` +| Scan timeout for the ICAP client. Defaults to '5m' (5 minutes). See the Environment Variable Types description for more details.| | xref:{s-path}/audit.adoc[audit] | `OCIS_EVENTS_AUTH_USERNAME` diff --git a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md index f2105f1f7..004a659e1 100644 --- a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md @@ -5,6 +5,7 @@ | ocis-pkg/shared/shared_types.go | `OCIS_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. | `false` | | services/antivirus/pkg/config/config.go | `OCIS_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | | | `OCIS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | +| | `ANTIVIRUS_ICAP_TIMEOUT` | Scan timeout for the ICAP client. Defaults to '5m' (5 minutes). See the Environment Variable Types description for more details. | | | services/audit/pkg/config/config.go | `OCIS_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | | | `OCIS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | | services/auth-service/pkg/config/config.go | `OCIS_TRACING_ENABLED;AUTH_SERVICE_TRACING_ENABLED` | Activates tracing. | | diff --git a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc index 8fd35b80d..04560ee38 100644 --- a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc @@ -6,7 +6,7 @@ [width="100%",cols="~,~,~,~",options="header"] |=== -| File| Variable| Replacement| Description +| Service| Variable| Replacement| Description | xref:{s-path}/auth-basic.adoc[auth-basic] | `LDAP_BIND_PASSWORD` From 46e3499a47feac0bf05d0583a5d699421af7fe37 Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Thu, 8 Feb 2024 15:24:40 +0100 Subject: [PATCH 059/112] enhancement: allow skipping service listing --- .../enhancement-skip-version-service-listing | 7 +++++ ocis/pkg/command/version.go | 30 ++++++++++++++----- 2 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 changelog/unreleased/enhancement-skip-version-service-listing diff --git a/changelog/unreleased/enhancement-skip-version-service-listing b/changelog/unreleased/enhancement-skip-version-service-listing new file mode 100644 index 000000000..b1e774105 --- /dev/null +++ b/changelog/unreleased/enhancement-skip-version-service-listing @@ -0,0 +1,7 @@ +Enhancement: allow to skip service listing + +The ocis version cmd listed all services by default. This is not always intended, +so we allow to skip the listing of the services by using the --skip-services flag. + +https://github.com/owncloud/ocis/pull/8408 +https://github.com/owncloud/ocis/issues/8070 diff --git a/ocis/pkg/command/version.go b/ocis/pkg/command/version.go index 3e251f953..e4c351b2f 100644 --- a/ocis/pkg/command/version.go +++ b/ocis/pkg/command/version.go @@ -5,29 +5,45 @@ import ( "os" tw "github.com/olekukonko/tablewriter" + "github.com/urfave/cli/v2" + mreg "go-micro.dev/v4/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/version" "github.com/owncloud/ocis/v2/ocis/pkg/register" - "github.com/urfave/cli/v2" - mreg "go-micro.dev/v4/registry" +) + +const ( + _skipServiceListingFlagName = "skip-services" ) // VersionCommand is the entrypoint for the version command. func VersionCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and all running service instances", + Name: "version", + Usage: "print the version of this binary and all running service instances", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: _skipServiceListingFlagName, + Usage: "skip service listing", + }, + }, Category: "info", Action: func(c *cli.Context) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) - fmt.Println("") + + if c.Bool(_skipServiceListingFlagName) { + return nil + } + + fmt.Print("\n") reg := registry.GetRegistry() serviceList, err := reg.ListServices() if err != nil { - fmt.Println(fmt.Errorf("could not list services: %v", err)) + fmt.Printf("could not list services: %v\n", err) return err } @@ -35,7 +51,7 @@ func VersionCommand(cfg *config.Config) *cli.Command { for _, s := range serviceList { s, err := reg.GetService(s.Name) if err != nil { - fmt.Println(fmt.Errorf("could not get service: %v", err)) + fmt.Printf("could not get service: %v\n", err) return err } services = append(services, s...) From ca8b06e896d70be3262d655010df98c3627fbc8c Mon Sep 17 00:00:00 2001 From: Roman Perekhod <2403905@gmail.com> Date: Thu, 8 Feb 2024 14:58:06 +0000 Subject: [PATCH 060/112] Automated changelog update [skip ci] --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ab8880bd..f03e8e307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ The following sections list the changes for unreleased. * Bugfix - Fix concurrent shares config: [#8317](https://github.com/owncloud/ocis/pull/8317) * Bugfix - Fix Content-Disposition header for downloads: [#8381](https://github.com/owncloud/ocis/pull/8381) * Bugfix - Signed url verification: [#8385](https://github.com/owncloud/ocis/pull/8385) +* Bugfix - Fix an error when move: [#8396](https://github.com/owncloud/ocis/pull/8396) * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) * Enhancement - Disable the password policy: [#7985](https://github.com/owncloud/ocis/pull/7985) * Enhancement - Update antivirus service: [#8062](https://github.com/owncloud/ocis/pull/8062) @@ -252,6 +253,14 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/8385 +* Bugfix - Fix an error when move: [#8396](https://github.com/owncloud/ocis/pull/8396) + + We fixed a bug that caused Internal Server Error when move using destination id + + https://github.com/owncloud/ocis/issues/6739 + https://github.com/owncloud/ocis/pull/8396 + https://github.com/cs3org/reva/pull/4503 + * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) Introduce a new environment variable `IDP_LOGIN_BACKGROUND_URL` that overrides From 912b568efdffc207ff01cefc197f1652ed9d7fa3 Mon Sep 17 00:00:00 2001 From: Christian Richter <1058116+dragonchaser@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:02:01 +0000 Subject: [PATCH 061/112] Automated changelog update [skip ci] --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f03e8e307..1cbb77e87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ The following sections list the changes for unreleased. * Enhancement - Modify the concurrency default: [#8309](https://github.com/owncloud/ocis/pull/8309) * Enhancement - Bump reva: [#8340](https://github.com/owncloud/ocis/pull/8340) * Enhancement - Allow sending multiple user ids in one sse event: [#8379](https://github.com/owncloud/ocis/pull/8379) +* Enhancement - Allow to skip service listing: [#8408](https://github.com/owncloud/ocis/pull/8408) ## Details @@ -347,6 +348,15 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/8379 https://github.com/cs3org/reva/pull/4501 +* Enhancement - Allow to skip service listing: [#8408](https://github.com/owncloud/ocis/pull/8408) + + The ocis version cmd listed all services by default. This is not always + intended, so we allow to skip the listing of the services by using the + --skip-services flag. + + https://github.com/owncloud/ocis/issues/8070 + https://github.com/owncloud/ocis/pull/8408 + # Changelog for [5.0.0-rc.1] (2023-12-27) The following sections list the changes for 5.0.0-rc.1. From fcea194af05e94b6c43b9428c40b178fc6d95249 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 8 Feb 2024 16:13:04 +0100 Subject: [PATCH 062/112] fix extended env var parsing Signed-off-by: jkoberg Co-authored-by: dragonchaser --- .../unreleased/fix-extended-env-parser.md | 6 +++ docs/helpers/extendedEnv.go | 18 +++---- docs/helpers/extended_vars.yaml | 48 +++++++++++++++++-- 3 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 changelog/unreleased/fix-extended-env-parser.md diff --git a/changelog/unreleased/fix-extended-env-parser.md b/changelog/unreleased/fix-extended-env-parser.md new file mode 100644 index 000000000..4f21c6ddd --- /dev/null +++ b/changelog/unreleased/fix-extended-env-parser.md @@ -0,0 +1,6 @@ +Bugfix: Fix extended env parser + +The extended envvar parser would be angry if there are two `os.Getenv` in the same line. +We fixed this. + +https://github.com/owncloud/ocis/pull/8409 diff --git a/docs/helpers/extendedEnv.go b/docs/helpers/extendedEnv.go index ad0904287..3dee523b9 100644 --- a/docs/helpers/extendedEnv.go +++ b/docs/helpers/extendedEnv.go @@ -85,18 +85,20 @@ func GetRogueEnvs() { } res := re.FindAllSubmatch([]byte(r[1]), -1) - if len(res) != 1 || len(res[0]) < 2 { + if len(res) < 1 { fmt.Printf("Error envvar not matching pattern: %s", r[1]) continue } - path := r[0] - name := strings.Trim(string(res[0][1]), "\"") - currentVars[path+name] = Variable{ - RawName: name, - Path: path, - FoundInCode: true, - Name: name, + for _, m := range res { + path := r[0] + name := strings.Trim(string(m[1]), "\"") + currentVars[path+name] = Variable{ + RawName: name, + Path: path, + FoundInCode: true, + Name: name, + } } } diff --git a/docs/helpers/extended_vars.yaml b/docs/helpers/extended_vars.yaml index a55ea1f37..544206b9a 100644 --- a/docs/helpers/extended_vars.yaml +++ b/docs/helpers/extended_vars.yaml @@ -34,7 +34,7 @@ variables: do_ignore: true - rawname: registryEnv path: ocis-pkg/registry/registry.go:118 - foundincode: true + foundincode: false name: MICRO_REGISTRY type: string default_value: "" @@ -45,7 +45,7 @@ variables: do_ignore: false - rawname: registryAddressEnv path: ocis-pkg/registry/registry.go:122 - foundincode: true + foundincode: false name: MICRO_REGISTRY_ADDRESS type: string default_value: 127.0.0.1:9233 @@ -54,7 +54,7 @@ variables: do_ignore: false - rawname: registryPasswordEnv path: ocis-pkg/registry/registry.go:115 - foundincode: true + foundincode: false name: MICRO_REGISTRY_AUTH_PASSWORD type: "" default_value: "" @@ -62,7 +62,7 @@ variables: do_ignore: false - rawname: registryUsernameEnv path: ocis-pkg/registry/registry.go:114 - foundincode: true + foundincode: false name: MICRO_REGISTRY_AUTH_USERNAME type: "" default_value: "" @@ -89,6 +89,46 @@ variables: for container images (inside the container) or '$HOME/.ocis/config' for binary releases. do_ignore: false +- rawname: _registryAddressEnv + path: ocis-pkg/natsjsregistry/registry.go:145 + foundincode: true + name: _registryAddressEnv + type: "" + default_value: "" + description: "" + do_ignore: false +- rawname: _registryAddressEnv + path: ocis-pkg/registry/registry.go:118 + foundincode: true + name: _registryAddressEnv + type: "" + default_value: "" + description: "" + do_ignore: false +- rawname: _registryEnv + path: ocis-pkg/registry/registry.go:114 + foundincode: true + name: _registryEnv + type: "" + default_value: "" + description: "" + do_ignore: false +- rawname: _registryPasswordEnv + path: ocis-pkg/natsjsregistry/registry.go:163 + foundincode: true + name: _registryPasswordEnv + type: "" + default_value: "" + description: "" + do_ignore: false +- rawname: _registryUsernameEnv + path: ocis-pkg/natsjsregistry/registry.go:163 + foundincode: true + name: _registryUsernameEnv + type: "" + default_value: "" + description: "" + do_ignore: false - rawname: parts[0] path: ocis-pkg/config/envdecode/envdecode.go:382 foundincode: true From 33e2119113df01644fc0a317234cfcf3462481cc Mon Sep 17 00:00:00 2001 From: Christian Richter <1058116+dragonchaser@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:23:09 +0000 Subject: [PATCH 063/112] Automated changelog update [skip ci] --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cbb77e87..b4e81947b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ The following sections list the changes for unreleased. * Bugfix - Fix Content-Disposition header for downloads: [#8381](https://github.com/owncloud/ocis/pull/8381) * Bugfix - Signed url verification: [#8385](https://github.com/owncloud/ocis/pull/8385) * Bugfix - Fix an error when move: [#8396](https://github.com/owncloud/ocis/pull/8396) +* Bugfix - Fix extended env parser: [#8409](https://github.com/owncloud/ocis/pull/8409) * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) * Enhancement - Disable the password policy: [#7985](https://github.com/owncloud/ocis/pull/7985) * Enhancement - Update antivirus service: [#8062](https://github.com/owncloud/ocis/pull/8062) @@ -262,6 +263,13 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/8396 https://github.com/cs3org/reva/pull/4503 +* Bugfix - Fix extended env parser: [#8409](https://github.com/owncloud/ocis/pull/8409) + + The extended envvar parser would be angry if there are two `os.Getenv` in the + same line. We fixed this. + + https://github.com/owncloud/ocis/pull/8409 + * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) Introduce a new environment variable `IDP_LOGIN_BACKGROUND_URL` that overrides From f89812c601274f3d9c32116096acefbbf75bcc1f Mon Sep 17 00:00:00 2001 From: mmattel Date: Thu, 8 Feb 2024 16:59:29 +0100 Subject: [PATCH 064/112] [docs-only] Fixes the content of extended_envars.yaml --- docs/helpers/extended_vars.yaml | 78 ++++++++++----------------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/docs/helpers/extended_vars.yaml b/docs/helpers/extended_vars.yaml index 544206b9a..4a3208d5f 100644 --- a/docs/helpers/extended_vars.yaml +++ b/docs/helpers/extended_vars.yaml @@ -32,39 +32,47 @@ variables: default_value: "" description: "" do_ignore: true -- rawname: registryEnv - path: ocis-pkg/registry/registry.go:118 - foundincode: false +- rawname: _registryEnv + path: ocis-pkg/registry/registry.go:114 + foundincode: true name: MICRO_REGISTRY type: string - default_value: "" + default_value: nats-js-kv description: 'The Go micro registry type to use. Supported types are: ''memory'', ''nats-js-kv'' (default) and ''kubernetes''. Note that ''nats'', ''etcd'', ''consul'' and ''mdns'' are deprecated and will be removed in a later version. Only change on supervision of ownCloud Support.' do_ignore: false -- rawname: registryAddressEnv - path: ocis-pkg/registry/registry.go:122 - foundincode: false +- rawname: _registryAddressEnv + path: ocis-pkg/natsjsregistry/registry.go:145 + foundincode: true name: MICRO_REGISTRY_ADDRESS type: string default_value: 127.0.0.1:9233 description: The bind address of the internal go micro framework. Only change on supervision of ownCloud Support. do_ignore: false -- rawname: registryPasswordEnv - path: ocis-pkg/registry/registry.go:115 - foundincode: false - name: MICRO_REGISTRY_AUTH_PASSWORD +- rawname: _registryAddressEnv + path: ocis-pkg/registry/registry.go:118 + foundincode: true + name: MICRO_REGISTRY_ADDRESS type: "" default_value: "" + description: "" + do_ignore: true +- rawname: _registryPasswordEnv + path: ocis-pkg/natsjsregistry/registry.go:163 + foundincode: true + name: MICRO_REGISTRY_AUTH_PASSWORD + type: string + default_value: "" description: Optional when using nats to authenticate with the nats cluster. do_ignore: false -- rawname: registryUsernameEnv - path: ocis-pkg/registry/registry.go:114 - foundincode: false +- rawname: _registryUsernameEnv + path: ocis-pkg/natsjsregistry/registry.go:163 + foundincode: true name: MICRO_REGISTRY_AUTH_USERNAME - type: "" + type: string default_value: "" description: Optional when using nats to authenticate with the nats cluster. do_ignore: false @@ -89,46 +97,6 @@ variables: for container images (inside the container) or '$HOME/.ocis/config' for binary releases. do_ignore: false -- rawname: _registryAddressEnv - path: ocis-pkg/natsjsregistry/registry.go:145 - foundincode: true - name: _registryAddressEnv - type: "" - default_value: "" - description: "" - do_ignore: false -- rawname: _registryAddressEnv - path: ocis-pkg/registry/registry.go:118 - foundincode: true - name: _registryAddressEnv - type: "" - default_value: "" - description: "" - do_ignore: false -- rawname: _registryEnv - path: ocis-pkg/registry/registry.go:114 - foundincode: true - name: _registryEnv - type: "" - default_value: "" - description: "" - do_ignore: false -- rawname: _registryPasswordEnv - path: ocis-pkg/natsjsregistry/registry.go:163 - foundincode: true - name: _registryPasswordEnv - type: "" - default_value: "" - description: "" - do_ignore: false -- rawname: _registryUsernameEnv - path: ocis-pkg/natsjsregistry/registry.go:163 - foundincode: true - name: _registryUsernameEnv - type: "" - default_value: "" - description: "" - do_ignore: false - rawname: parts[0] path: ocis-pkg/config/envdecode/envdecode.go:382 foundincode: true From 5f577409761914946122bd65b552b9892b71e2e6 Mon Sep 17 00:00:00 2001 From: Sabin Panta <64484313+S-Panta@users.noreply.github.com> Date: Fri, 9 Feb 2024 14:02:38 +0545 Subject: [PATCH 065/112] [tests-only][full-ci]resourcetype variable deleted from testcode (#8370) * resourcetype variable deleted from testcode * updated expected failures file * addressing review --- ...ected-failures-localAPI-on-OCIS-storage.md | 26 ++++---- .../apiSharingNg/deletePermissions.feature | 64 ++++++++----------- .../features/apiSharingNg/linkShare.feature | 16 ----- .../features/bootstrap/SharingNgContext.php | 32 ++-------- 4 files changed, 48 insertions(+), 90 deletions(-) diff --git a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md index 6f729a599..b8789d196 100644 --- a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md @@ -249,19 +249,19 @@ The expected failures in this file are from features in the owncloud/ocis repo. ### [blocksDownload link type is not implemented yet (sharing-ng)](https://github.com/owncloud/ocis/issues/7879) -- [apiSharingNg/linkShare.feature:79](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L79) -- [apiSharingNg/linkShare.feature:149](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L149) -- [apiSharingNg/linkShare.feature:228](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L228) -- [apiSharingNg/linkShare.feature:305](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L305) -- [apiSharingNg/linkShare.feature:377](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L377) -- [apiSharingNg/linkShare.feature:451](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L451) -- [apiSharingNg/linkShare.feature:453](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L453) -- [apiSharingNg/linkShare.feature:455](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L455) -- [apiSharingNg/linkShare.feature:456](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L456) -- [apiSharingNg/deletePermissions.feature:130](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L130) -- [apiSharingNg/deletePermissions.feature:147](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L147) -- [apiSharingNg/deletePermissions.feature:168](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L168) -- [apiSharingNg/deletePermissions.feature:187](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L187) +- [apiSharingNg/linkShare.feature:78](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L78) +- [apiSharingNg/linkShare.feature:147](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L147) +- [apiSharingNg/linkShare.feature:225](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L225) +- [apiSharingNg/linkShare.feature:301](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L301) +- [apiSharingNg/linkShare.feature:372](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L372) +- [apiSharingNg/linkShare.feature:444](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L444) +- [apiSharingNg/linkShare.feature:446](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L446) +- [apiSharingNg/linkShare.feature:448](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L448) +- [apiSharingNg/linkShare.feature:449](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L449) +- [apiSharingNg/deletePermissions.feature:125](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L125) +- [apiSharingNg/deletePermissions.feature:141](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L141) +- [apiSharingNg/deletePermissions.feature:161](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L161) +- [apiSharingNg/deletePermissions.feature:179](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L179) ### [sharee (editor role) MOVE a file by file-id into same shared folder returns 403](https://github.com/owncloud/ocis/issues/7617) diff --git a/tests/acceptance/features/apiSharingNg/deletePermissions.feature b/tests/acceptance/features/apiSharingNg/deletePermissions.feature index 222514e4c..ffc90cb12 100644 --- a/tests/acceptance/features/apiSharingNg/deletePermissions.feature +++ b/tests/acceptance/features/apiSharingNg/deletePermissions.feature @@ -13,23 +13,22 @@ Feature: Remove access to a drive item Given user "Alice" has created folder "FolderToShare" And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile.txt" And user "Alice" has sent the following share invitation: - | resourceType | | | resource | | | space | Personal | | sharee | Brian | | shareType | user | | permissionsRole | | - When user "Alice" removes the share permission of user "Brian" from "" of space "Personal" using the Graph API + When user "Alice" removes the share permission of user "Brian" from resource "" of space "Personal" using the Graph API Then the HTTP status code should be "204" And for user "Brian" the space "Shares" should not contain these entries: | | Examples: - | permissionsRole | resource-type | path | - | Viewer | file | textfile.txt | - | File Editor | file | textfile.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissionsRole | path | + | Viewer | textfile.txt | + | File Editor | textfile.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: user removes access to resource inside of a project space in the user share @@ -38,23 +37,22 @@ Feature: Remove access to a drive item And user "Alice" has created a folder "FolderToShare" in space "NewSpace" And user "Alice" has uploaded a file inside space "NewSpace" with content "some content" to "textfile.txt" And user "Alice" has sent the following share invitation: - | resourceType | | | resource | | | space | NewSpace | | sharee | Brian | | shareType | user | | permissionsRole | | - When user "Alice" removes the share permission of user "Brian" from "" of space "NewSpace" using the Graph API + When user "Alice" removes the share permission of user "Brian" from resource "" of space "NewSpace" using the Graph API Then the HTTP status code should be "204" And for user "Brian" the space "Shares" should not contain these entries: | | Examples: - | permissionsRole | resource-type | path | - | Viewer | file | textfile.txt | - | File Editor | file | textfile.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissionsRole | path | + | Viewer | textfile.txt | + | File Editor | textfile.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: user removes access to a resource in a group share @@ -64,23 +62,22 @@ Feature: Remove access to a drive item And user "Alice" has created folder "FolderToShare" And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile.txt" And user "Alice" has sent the following share invitation: - | resourceType | | | resource | | | space | Personal | | sharee | group1 | | shareType | group | | permissionsRole | | - When user "Alice" removes the share permission of group "group1" from "" of space "Personal" using the Graph API + When user "Alice" removes the share permission of group "group1" from resource "" of space "Personal" using the Graph API Then the HTTP status code should be "204" And for user "Brian" the space "Shares" should not contain these entries: | | Examples: - | permissionsRole | resource-type | path | - | Viewer | file | textfile.txt | - | File Editor | file | textfile.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissionsRole | path | + | Viewer | textfile.txt | + | File Editor | textfile.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: user removes access to a resource inside of a project space in group share @@ -92,29 +89,27 @@ Feature: Remove access to a drive item And user "Brian" has been added to group "group1" And user "Alice" has been added to group "group1" And user "Alice" has sent the following share invitation: - | resourceType | | | resource | | | space | NewSpace | | sharee | group1 | | shareType | group | | permissionsRole | | - When user "Alice" removes the share permission of group "group1" from "" of space "NewSpace" using the Graph API + When user "Alice" removes the share permission of group "group1" from resource "" of space "NewSpace" using the Graph API Then the HTTP status code should be "204" And for user "Brian" the space "Shares" should not contain these entries: | | Examples: - | permissionsRole | resource-type | path | - | Viewer | file | textfile.txt | - | File Editor | file | textfile.txt | - | Viewer | folder | FolderToShare | - | Editor | folder | FolderToShare | - | Uploader | folder | FolderToShare | + | permissionsRole | path | + | Viewer | textfile.txt | + | File Editor | textfile.txt | + | Viewer | FolderToShare | + | Editor | FolderToShare | + | Uploader | FolderToShare | Scenario Outline: user removes access to a folder in link share Given user "Alice" has created folder "FolderToShare" And user "Alice" has created the following link share: - | resourceType | folder | | resource | FolderToShare | | space | Personal | | permissionsRole | | @@ -133,7 +128,6 @@ Feature: Remove access to a drive item Scenario Outline: user removes access to a file in link share Given user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile.txt" And user "Alice" has created the following link share: - | resourceType | file | | resource | textfile.txt | | space | Personal | | permissionsRole | | @@ -152,7 +146,6 @@ Feature: Remove access to a drive item And user "Alice" has created a space "NewSpace" with the default quota using the Graph API And user "Alice" has created a folder "FolderToShare" in space "NewSpace" And user "Alice" has created the following link share: - | resourceType | folder | | resource | FolderToShare | | space | NewSpace | | permissionsRole | | @@ -173,7 +166,6 @@ Feature: Remove access to a drive item And user "Alice" has created a space "NewSpace" with the default quota using the Graph API And user "Alice" has uploaded a file inside space "NewSpace" with content "some content" to "textfile.txt" And user "Alice" has created the following link share: - | resourceType | file | | resource | textfile.txt | | space | NewSpace | | permissionsRole | | diff --git a/tests/acceptance/features/apiSharingNg/linkShare.feature b/tests/acceptance/features/apiSharingNg/linkShare.feature index b7259c17a..23a419d97 100644 --- a/tests/acceptance/features/apiSharingNg/linkShare.feature +++ b/tests/acceptance/features/apiSharingNg/linkShare.feature @@ -10,7 +10,6 @@ Feature: Create a share link for a resource Scenario Outline: create a link share of a folder Given user "Alice" has created folder "folder" When user "Alice" creates the following link share using the Graph API: - | resourceType | folder | | resource | folder | | space | Personal | | permissionsRole | | @@ -82,7 +81,6 @@ Feature: Create a share link for a resource Scenario Outline: create a link share of a file Given user "Alice" has uploaded file with content "other data" to "textfile1.txt" When user "Alice" creates the following link share using the Graph API: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | permissionsRole | | @@ -152,7 +150,6 @@ Feature: Create a share link for a resource Scenario Outline: create a link share of a folder with display name and expiry date Given user "Alice" has created folder "folder" When user "Alice" creates the following link share using the Graph API: - | resourceType | folder | | resource | folder | | space | Personal | | permissionsRole | | @@ -231,7 +228,6 @@ Feature: Create a share link for a resource Scenario Outline: create a link share of a file with display name and expiry date Given user "Alice" has uploaded file with content "other data" to "textfile1.txt" When user "Alice" creates the following link share using the Graph API: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | permissionsRole | | @@ -311,7 +307,6 @@ Feature: Create a share link for a resource | OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false | And user "Alice" has uploaded file with content "other data" to "textfile1.txt" When user "Alice" creates the following link share using the Graph API: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | permissionsRole | | @@ -380,13 +375,11 @@ Feature: Create a share link for a resource Scenario Outline: update role of a file's link share Given user "Alice" has uploaded file with content "other data" to "textfile1.txt" And user "Alice" has created the following link share: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | permissionsRole | | | password | %public% | When user "Alice" updates the last public link share using the Graph API with - | resourceType | file | | resource | textfile1.txt | | space | Personal | | permissionsRole | | @@ -460,14 +453,12 @@ Feature: Create a share link for a resource Scenario: update expiration date of a file's link share Given user "Alice" has uploaded file with content "other data" to "textfile1.txt" And user "Alice" has created the following link share: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | permissionsRole | view | | password | %public% | | expirationDateTime | 2200-07-15T14:00:00.000Z | When user "Alice" updates the last public link share using the Graph API with - | resourceType | file | | resource | textfile1.txt | | space | Personal | | expirationDateTime | 2201-07-15T14:00:00.000Z | @@ -534,12 +525,10 @@ Feature: Create a share link for a resource | OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false | And user "Alice" has uploaded file with content "other data" to "textfile1.txt" And user "Alice" has created the following link share: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | permissionsRole | view | When user "Alice" sets the following password for the last link share using the Graph API: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | password | %public% | @@ -564,13 +553,11 @@ Feature: Create a share link for a resource Scenario: update password of a file's link share Given user "Alice" has uploaded file with content "other data" to "textfile1.txt" And user "Alice" has created the following link share: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | permissionsRole | view | | password | $heLlo*1234* | When user "Alice" sets the following password for the last link share using the Graph API: - | resourceType | file | | resource | textfile1.txt | | space | Personal | | password | %public% | @@ -598,7 +585,6 @@ Feature: Create a share link for a resource Given the config "OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" has been set to path "config/drone/banned-password-list.txt" And user "Alice" has uploaded file with content "other data" to "text.txt" When user "Alice" creates the following link share using the Graph API: - | resourceType | file | | resource | text.txt | | space | Personal | | permissionsRole | view | @@ -645,13 +631,11 @@ Feature: Create a share link for a resource Given the config "OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" has been set to path "config/drone/banned-password-list.txt" And user "Alice" has uploaded file with content "other data" to "text.txt" And user "Alice" has created the following link share: - | resourceType | file | | resource | text.txt | | space | Personal | | permissionsRole | view | | password | %public% | When user "Alice" sets the following password for the last link share using the Graph API: - | resourceType | file | | resource | text.txt | | space | Personal | | permissionsRole | view | diff --git a/tests/acceptance/features/bootstrap/SharingNgContext.php b/tests/acceptance/features/bootstrap/SharingNgContext.php index bdd91d4d5..4c6453ea1 100644 --- a/tests/acceptance/features/bootstrap/SharingNgContext.php +++ b/tests/acceptance/features/bootstrap/SharingNgContext.php @@ -65,15 +65,10 @@ class SharingNgContext implements Context { public function createLinkShare(string $user, TableNode $body): ResponseInterface { $bodyRows = $body->getRowsHash(); $space = $bodyRows['space']; - $resourceType = $bodyRows['resourceType']; $resource = $bodyRows['resource']; $spaceId = ($this->spacesContext->getSpaceByName($user, $space))["id"]; - if ($resourceType === 'folder') { - $itemId = $this->spacesContext->getResourceId($user, $space, $resource); - } else { - $itemId = $this->spacesContext->getFileId($user, $space, $resource); - } + $itemId = $this->spacesContext->getResourceId($user, $space, $resource); $bodyRows['displayName'] = $bodyRows['displayName'] ?? null; $bodyRows['expirationDateTime'] = $bodyRows['expirationDateTime'] ?? null; @@ -399,14 +394,9 @@ class SharingNgContext implements Context { public function userSetsOrUpdatesFollowingPasswordForLastLinkShareUsingTheGraphApi(string $user, TableNode $body):void { $bodyRows = $body->getRowsHash(); $space = $bodyRows['space']; - $resourceType = $bodyRows['resourceType']; $resource = $bodyRows['resource']; $spaceId = ($this->spacesContext->getSpaceByName($user, $space))["id"]; - if ($resourceType === 'folder') { - $itemId = $this->spacesContext->getResourceId($user, $space, $resource); - } else { - $itemId = $this->spacesContext->getFileId($user, $space, $resource); - } + $itemId = $this->spacesContext->getResourceId($user, $space, $resource); if (\array_key_exists('password', $bodyRows)) { $body = [ @@ -432,7 +422,6 @@ class SharingNgContext implements Context { /** * @param string $sharer * @param string $shareType (user|group) - * @param string $resourceType * @param string $resource * @param string $space * @param string|null $sharee can be both user or group @@ -444,15 +433,12 @@ class SharingNgContext implements Context { public function removeSharePermission( string $sharer, string $shareType, - string $resourceType, string $resource, string $space, ?string $sharee = null ): ResponseInterface { $spaceId = ($this->spacesContext->getSpaceByName($sharer, $space))["id"]; - $itemId = ($resourceType === 'folder') - ? $this->spacesContext->getResourceId($sharer, $space, $resource) - : $this->spacesContext->getFileId($sharer, $space, $resource); + $itemId = $this->spacesContext->getResourceId($sharer, $space, $resource); $permId = ($shareType === 'link') ? $this->featureContext->shareNgGetLastCreatedLinkShareID() @@ -470,12 +456,11 @@ class SharingNgContext implements Context { } /** - * @When /^user "([^"]*)" removes the share permission of (user|group) "([^"]*)" from (file|folder) "([^"]*)" of space "([^"]*)" using the Graph API$/ + * @When /^user "([^"]*)" removes the share permission of (user|group) "([^"]*)" from (?:file|folder|resource) "([^"]*)" of space "([^"]*)" using the Graph API$/ * * @param string $sharer * @param string $shareType (user|group) * @param string $sharee can be both user or group - * @param string $resourceType * @param string $resource * @param string $space * @@ -487,20 +472,18 @@ class SharingNgContext implements Context { string $sharer, string $shareType, string $sharee, - string $resourceType, string $resource, string $space ): void { $this->featureContext->setResponse( - $this->removeSharePermission($sharer, $shareType, $resourceType, $resource, $space) + $this->removeSharePermission($sharer, $shareType, $resource, $space) ); } /** - * @When /^user "([^"]*)" removes the share permission of link from (file|folder) "([^"]*)" of space "([^"]*)" using the Graph API$/ + * @When /^user "([^"]*)" removes the share permission of link from (?:file|folder) "([^"]*)" of space "([^"]*)" using the Graph API$/ * * @param string $sharer - * @param string $resourceType * @param string $resource * @param string $space * @@ -510,12 +493,11 @@ class SharingNgContext implements Context { */ public function userRemovesSharePermissionOfAResourceInLinkShareUsingGraphAPI( string $sharer, - string $resourceType, string $resource, string $space ):void { $this->featureContext->setResponse( - $this->removeSharePermission($sharer, 'link', $resourceType, $resource, $space) + $this->removeSharePermission($sharer, 'link', $resource, $space) ); } From 358f3d066734523fe8bedbf731457f2734879c5e Mon Sep 17 00:00:00 2001 From: mmattel Date: Fri, 9 Feb 2024 09:30:07 +0100 Subject: [PATCH 066/112] [docs-only] Add MICRO_REGISTRY_xxx to the added envars list --- .../env-var-deltas/4.0.0-5.0.0-added.adoc | 11 ++++++++++- .../general-info/env-var-deltas/4.0.0-5.0.0-added.md | 5 ++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc index 6edb3679c..061e54b1c 100644 --- a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc @@ -8,9 +8,18 @@ |=== | Service| Variable| Description| Default -| Available in multiple services +| xref:deployment/services/env-vars-special-scope.adoc[Special Scope Envvars] | `OCIS_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured.| `false` +| +| `MICRO_REGISTRY` (important change) +| The Go micro registry type to use. Supported types are: 'memory', 'nats-js-kv' (default) and 'kubernetes'. Note that 'nats', 'etcd', 'consul' and 'mdns' are deprecated and will be removed in a later version. Only change on supervision of ownCloud Support.| nats-js-kv +| +| `MICRO_REGISTRY_AUTH_PASSWORD` +| Optional when using nats to authenticate with the nats cluster.| +| +| `MICRO_REGISTRY_AUTH_USERNAME` +| Optional when using nats to authenticate with the nats cluster.| | xref:{s-path}/antivirus.adoc[antivirus] | `OCIS_EVENTS_AUTH_USERNAME` diff --git a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md index 004a659e1..a58a4ef27 100644 --- a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md @@ -3,9 +3,12 @@ | File | Variable | Description | Default | |-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------| | ocis-pkg/shared/shared_types.go | `OCIS_CACHE_DISABLE_PERSISTENCE` | Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. | `false` | +| ocis-pkg/registry/registry.go | `MICRO_REGISTRY` (important change) | The Go micro registry type to use. Supported types are: 'memory', 'nats-js-kv' (default) and 'kubernetes'. Note that 'nats', 'etcd', 'consul' and 'mdns' are deprecated and will be removed in a later version. Only change on supervision of ownCloud Support. | `nats-js-kv` | +| ocis-pkg/natsjsregistry/registry.go | `MICRO_REGISTRY_AUTH_USERNAME` | Optional when using nats to authenticate with the nats cluster. | | +| | `MICRO_REGISTRY_AUTH_PASSWORD` | Optional when using nats to authenticate with the nats cluster. | | | services/antivirus/pkg/config/config.go | `OCIS_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | | | `OCIS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | -| | `ANTIVIRUS_ICAP_TIMEOUT` | Scan timeout for the ICAP client. Defaults to '5m' (5 minutes). See the Environment Variable Types description for more details. | | +| | `ANTIVIRUS_ICAP_TIMEOUT` | Scan timeout for the ICAP client. Defaults to '5m' (5 minutes). See the Environment Variable Types description for more details. | | | services/audit/pkg/config/config.go | `OCIS_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | | | `OCIS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | | services/auth-service/pkg/config/config.go | `OCIS_TRACING_ENABLED;AUTH_SERVICE_TRACING_ENABLED` | Activates tracing. | | From 17a799c1fa57058ad47225e6b3240ecf3a1ba128 Mon Sep 17 00:00:00 2001 From: Roman Perekhod Date: Mon, 27 Nov 2023 22:12:38 +0100 Subject: [PATCH 067/112] fix search response --- changelog/unreleased/fix-search-responce.md | 6 ++++++ services/search/pkg/engine/bleve.go | 4 ++++ services/search/pkg/query/{kql => }/error.go | 10 ++++++++- services/search/pkg/query/kql/cast.go | 5 +++-- .../search/pkg/query/kql/dictionary_test.go | 21 ++++++++++--------- services/search/pkg/query/kql/kql_test.go | 3 ++- services/search/pkg/query/kql/validate.go | 8 +++---- 7 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 changelog/unreleased/fix-search-responce.md rename services/search/pkg/query/{kql => }/error.go (82%) diff --git a/changelog/unreleased/fix-search-responce.md b/changelog/unreleased/fix-search-responce.md new file mode 100644 index 000000000..e7f308a15 --- /dev/null +++ b/changelog/unreleased/fix-search-responce.md @@ -0,0 +1,6 @@ +Bugfix: Fix search response + +We fixed the search response code from 500 to 400 when the request is invalid + +https://github.com/owncloud/ocis/pull/7815 +https://github.com/owncloud/ocis/issues/7812 diff --git a/services/search/pkg/engine/bleve.go b/services/search/pkg/engine/bleve.go index a0b3f26f3..54375d992 100644 --- a/services/search/pkg/engine/bleve.go +++ b/services/search/pkg/engine/bleve.go @@ -22,6 +22,7 @@ import ( storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" @@ -123,6 +124,9 @@ func BuildBleveMapping() (mapping.IndexMapping, error) { func (b *Bleve) Search(ctx context.Context, sir *searchService.SearchIndexRequest) (*searchService.SearchIndexResponse, error) { createdQuery, err := b.queryCreator.Create(sir.Query) if err != nil { + if searchQuery.IsValidationError(err) { + return nil, errtypes.BadRequest(err.Error()) + } return nil, err } diff --git a/services/search/pkg/query/kql/error.go b/services/search/pkg/query/error.go similarity index 82% rename from services/search/pkg/query/kql/error.go rename to services/search/pkg/query/error.go index 5f171d77f..89f504c8a 100644 --- a/services/search/pkg/query/kql/error.go +++ b/services/search/pkg/query/error.go @@ -1,4 +1,4 @@ -package kql +package query import ( "fmt" @@ -37,3 +37,11 @@ type UnsupportedTimeRangeError struct { func (e UnsupportedTimeRangeError) Error() string { return fmt.Sprintf("unable to convert '%v' to a time range", e.Value) } + +func IsValidationError(err error) bool { + switch err.(type) { + case *StartsWithBinaryOperatorError, *NamedGroupInvalidNodesError, *UnsupportedTimeRangeError: + return true + } + return false +} diff --git a/services/search/pkg/query/kql/cast.go b/services/search/pkg/query/kql/cast.go index 06fb800ce..c6ca30541 100644 --- a/services/search/pkg/query/kql/cast.go +++ b/services/search/pkg/query/kql/cast.go @@ -5,6 +5,7 @@ import ( "time" "github.com/jinzhu/now" + "github.com/owncloud/ocis/v2/services/search/pkg/query" "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" ) @@ -84,7 +85,7 @@ func toTimeRange(in interface{}) (*time.Time, *time.Time, error) { value, err := toString(in) if err != nil { - return &from, &to, UnsupportedTimeRangeError{} + return &from, &to, &query.UnsupportedTimeRangeError{} } c := &now.Config{ @@ -131,7 +132,7 @@ func toTimeRange(in interface{}) (*time.Time, *time.Time, error) { } if from.IsZero() || to.IsZero() { - return nil, nil, UnsupportedTimeRangeError{} + return nil, nil, &query.UnsupportedTimeRangeError{} } return &from, &to, nil diff --git a/services/search/pkg/query/kql/dictionary_test.go b/services/search/pkg/query/kql/dictionary_test.go index ba2780db8..adf4bd2d4 100644 --- a/services/search/pkg/query/kql/dictionary_test.go +++ b/services/search/pkg/query/kql/dictionary_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/jinzhu/now" + "github.com/owncloud/ocis/v2/services/search/pkg/query" tAssert "github.com/stretchr/testify/assert" "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" @@ -34,13 +35,13 @@ func TestParse_Spec(t *testing.T) { }, { name: `AND`, - error: kql.StartsWithBinaryOperatorError{ + error: query.StartsWithBinaryOperatorError{ Node: &ast.OperatorNode{Value: kql.BoolAND}, }, }, { name: `AND cat AND dog`, - error: kql.StartsWithBinaryOperatorError{ + error: query.StartsWithBinaryOperatorError{ Node: &ast.OperatorNode{Value: kql.BoolAND}, }, }, @@ -80,13 +81,13 @@ func TestParse_Spec(t *testing.T) { }, { name: `OR`, - error: kql.StartsWithBinaryOperatorError{ + error: query.StartsWithBinaryOperatorError{ Node: &ast.OperatorNode{Value: kql.BoolOR}, }, }, { name: `OR cat AND dog`, - error: kql.StartsWithBinaryOperatorError{ + error: query.StartsWithBinaryOperatorError{ Node: &ast.OperatorNode{Value: kql.BoolOR}, }, }, @@ -862,37 +863,37 @@ func TestParse_Errors(t *testing.T) { tests := []testCase{ { query: "animal:(mammal:cat mammal:dog reptile:turtle)", - error: kql.NamedGroupInvalidNodesError{ + error: query.NamedGroupInvalidNodesError{ Node: &ast.StringNode{Key: "mammal", Value: "cat"}, }, }, { query: "animal:(cat mammal:dog turtle)", - error: kql.NamedGroupInvalidNodesError{ + error: query.NamedGroupInvalidNodesError{ Node: &ast.StringNode{Key: "mammal", Value: "dog"}, }, }, { query: "animal:(AND cat)", - error: kql.StartsWithBinaryOperatorError{ + error: query.StartsWithBinaryOperatorError{ Node: &ast.OperatorNode{Value: kql.BoolAND}, }, }, { query: "animal:(OR cat)", - error: kql.StartsWithBinaryOperatorError{ + error: query.StartsWithBinaryOperatorError{ Node: &ast.OperatorNode{Value: kql.BoolOR}, }, }, { query: "(AND cat)", - error: kql.StartsWithBinaryOperatorError{ + error: query.StartsWithBinaryOperatorError{ Node: &ast.OperatorNode{Value: kql.BoolAND}, }, }, { query: "(OR cat)", - error: kql.StartsWithBinaryOperatorError{ + error: query.StartsWithBinaryOperatorError{ Node: &ast.OperatorNode{Value: kql.BoolOR}, }, }, diff --git a/services/search/pkg/query/kql/kql_test.go b/services/search/pkg/query/kql/kql_test.go index de050a474..c130f7558 100644 --- a/services/search/pkg/query/kql/kql_test.go +++ b/services/search/pkg/query/kql/kql_test.go @@ -3,6 +3,7 @@ package kql_test import ( "testing" + "github.com/owncloud/ocis/v2/services/search/pkg/query" tAssert "github.com/stretchr/testify/assert" "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" @@ -22,7 +23,7 @@ func TestNewAST(t *testing.T) { { name: "error", givenQuery: kql.BoolAND, - expectedError: kql.StartsWithBinaryOperatorError{ + expectedError: query.StartsWithBinaryOperatorError{ Node: &ast.OperatorNode{Value: kql.BoolAND}, }, }, diff --git a/services/search/pkg/query/kql/validate.go b/services/search/pkg/query/kql/validate.go index 9e9a2428b..96c6761ab 100644 --- a/services/search/pkg/query/kql/validate.go +++ b/services/search/pkg/query/kql/validate.go @@ -1,6 +1,7 @@ package kql import ( + "github.com/owncloud/ocis/v2/services/search/pkg/query" "github.com/owncloud/ocis/v2/services/search/pkg/query/ast" ) @@ -9,10 +10,9 @@ func validateAst(a *ast.Ast) error { case *ast.OperatorNode: switch node.Value { case BoolAND, BoolOR: - return StartsWithBinaryOperatorError{Node: node} + return &query.StartsWithBinaryOperatorError{Node: node} } } - return nil } @@ -21,14 +21,14 @@ func validateGroupNode(n *ast.GroupNode) error { case *ast.OperatorNode: switch node.Value { case BoolAND, BoolOR: - return StartsWithBinaryOperatorError{Node: node} + return &query.StartsWithBinaryOperatorError{Node: node} } } if n.Key != "" { for _, node := range n.Nodes { if ast.NodeKey(node) != "" { - return NamedGroupInvalidNodesError{Node: node} + return &query.NamedGroupInvalidNodesError{Node: node} } } } From 4b1b1c24c84591e8cba280f341b12f533ecf7541 Mon Sep 17 00:00:00 2001 From: Roman Perekhod <2403905@gmail.com> Date: Fri, 9 Feb 2024 09:12:51 +0000 Subject: [PATCH 068/112] Automated changelog update [skip ci] --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e81947b..2ce8099c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ The following sections list the changes for unreleased. ## Summary +* Bugfix - Fix search response: [#7815](https://github.com/owncloud/ocis/pull/7815) * Bugfix - The race conditions in tests: [#7847](https://github.com/owncloud/ocis/pull/7847) * Bugfix - Fix jwt config of policies service: [#7893](https://github.com/owncloud/ocis/pull/7893) * Bugfix - Fix Service Account Roles on external IDP: [#8074](https://github.com/owncloud/ocis/pull/8074) @@ -79,6 +80,13 @@ The following sections list the changes for unreleased. ## Details +* Bugfix - Fix search response: [#7815](https://github.com/owncloud/ocis/pull/7815) + + We fixed the search response code from 500 to 400 when the request is invalid + + https://github.com/owncloud/ocis/issues/7812 + https://github.com/owncloud/ocis/pull/7815 + * Bugfix - The race conditions in tests: [#7847](https://github.com/owncloud/ocis/pull/7847) We fixed the race conditions in tests. From 499a484ec0ed35715b345356e4a10d196d2cacfe Mon Sep 17 00:00:00 2001 From: mmattel Date: Fri, 9 Feb 2024 10:36:12 +0100 Subject: [PATCH 069/112] fix defaults and adapt descriptions --- docs/helpers/extended_vars.yaml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/helpers/extended_vars.yaml b/docs/helpers/extended_vars.yaml index 4a3208d5f..28091b1a8 100644 --- a/docs/helpers/extended_vars.yaml +++ b/docs/helpers/extended_vars.yaml @@ -81,21 +81,20 @@ variables: foundincode: true name: OCIS_BASE_DATA_PATH type: string - default_value: '''/var/lib/ocis'' or ''$HOME/.ocis/''' + default_value: "" description: The base directory location used by several services and for user data. - Predefined to '/var/lib/ocis' for container images (inside the container) or '$HOME/.ocis/' - for binary releases. Services can have, if available, an individual setting with - an own environment variable. + See the General Info section in the documentation for more details on defaults. + Services can have, if available, an individual setting with an own environment + variable. do_ignore: false - rawname: OCIS_CONFIG_DIR path: ocis-pkg/config/defaults/paths.go:56 foundincode: true name: OCIS_CONFIG_DIR type: string - default_value: '''/etc/ocis'' or ''$HOME/.ocis/config''' - description: The default directory location for config files. Predefined to '/etc/ocis' - for container images (inside the container) or '$HOME/.ocis/config' for binary - releases. + default_value: "" + description: The default directory location for config files. See the General Info + section in the documentation for more details on defaults. do_ignore: false - rawname: parts[0] path: ocis-pkg/config/envdecode/envdecode.go:382 From fb5f29c537ecfc9895e29dc1ca89d3f6578659fa Mon Sep 17 00:00:00 2001 From: Roman Perekhod Date: Thu, 8 Feb 2024 21:56:14 +0100 Subject: [PATCH 070/112] fix the bug when the expiration dates can't be removed from link permissions --- changelog/unreleased/sharing-ng-expirationdate.md | 6 ++++++ services/graph/pkg/service/v0/links.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/sharing-ng-expirationdate.md diff --git a/changelog/unreleased/sharing-ng-expirationdate.md b/changelog/unreleased/sharing-ng-expirationdate.md new file mode 100644 index 000000000..f917b3111 --- /dev/null +++ b/changelog/unreleased/sharing-ng-expirationdate.md @@ -0,0 +1,6 @@ +Bugfix: graph/drives/permission Expiration date update + +We fixed a bug in the Update sharing permission the expiration dates can't be removed from link permissions. + +https://github.com/owncloud/ocis/pull/8413 +https://github.com/owncloud/ocis/issues/8405 diff --git a/services/graph/pkg/service/v0/links.go b/services/graph/pkg/service/v0/links.go index f9303fa5c..684dd8e67 100644 --- a/services/graph/pkg/service/v0/links.go +++ b/services/graph/pkg/service/v0/links.go @@ -202,7 +202,7 @@ func (g Graph) libreGraphPermissionFromCS3PublicShare(createdLink *link.PublicSh } func parseAndFillUpTime(t *time.Time) *types.Timestamp { - if t == nil { + if t == nil || t.IsZero() { return nil } From c99b3fac63c0c4fa0344e1ef0c2270690f7e62ed Mon Sep 17 00:00:00 2001 From: Roman Perekhod Date: Fri, 9 Feb 2024 09:39:10 +0000 Subject: [PATCH 071/112] Automated changelog update [skip ci] --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ce8099c5..d5f22dc93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ The following sections list the changes for unreleased. * Bugfix - Signed url verification: [#8385](https://github.com/owncloud/ocis/pull/8385) * Bugfix - Fix an error when move: [#8396](https://github.com/owncloud/ocis/pull/8396) * Bugfix - Fix extended env parser: [#8409](https://github.com/owncloud/ocis/pull/8409) +* Bugfix - Graph/drives/permission Expiration date update: [#8413](https://github.com/owncloud/ocis/pull/8413) * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) * Enhancement - Disable the password policy: [#7985](https://github.com/owncloud/ocis/pull/7985) * Enhancement - Update antivirus service: [#8062](https://github.com/owncloud/ocis/pull/8062) @@ -278,6 +279,14 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/8409 +* Bugfix - Graph/drives/permission Expiration date update: [#8413](https://github.com/owncloud/ocis/pull/8413) + + We fixed a bug in the Update sharing permission the expiration dates can't be + removed from link permissions. + + https://github.com/owncloud/ocis/issues/8405 + https://github.com/owncloud/ocis/pull/8413 + * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) Introduce a new environment variable `IDP_LOGIN_BACKGROUND_URL` that overrides From 3d3f8949f5d931b58a26d78e8b96de04d9af6b7b Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Fri, 9 Feb 2024 09:36:17 +0100 Subject: [PATCH 072/112] Bump mockery to 2.40.2 to address issues when building with go1.22: https://github.com/vektra/mockery/pull/753 --- .bingo/Variables.mk | 6 +++--- .bingo/mockery.mod | 2 +- .bingo/mockery.sum | 6 ++++++ .bingo/variables.env | 2 +- ocis-pkg/oidc/mocks/oidc_client.go | 2 +- services/graph/mocks/http_client.go | 2 +- services/graph/mocks/permissions.go | 2 +- services/graph/mocks/publisher.go | 2 +- services/graph/mocks/role_service.go | 2 +- services/graph/pkg/identity/mocks/backend.go | 2 +- services/graph/pkg/identity/mocks/education_backend.go | 2 +- services/graph/pkg/identity/mocks/ldapclient.go | 2 +- services/invitations/pkg/backends/keycloak/mocks/client.go | 2 +- services/proxy/pkg/user/backend/mocks/user_backend.go | 2 +- services/proxy/pkg/userroles/mocks/user_role_assigner.go | 2 +- services/search/pkg/content/mocks/extractor.go | 2 +- services/search/pkg/content/mocks/retriever.go | 2 +- services/search/pkg/engine/mocks/engine.go | 2 +- services/search/pkg/search/mocks/searcher.go | 2 +- services/settings/pkg/settings/mocks/manager.go | 2 +- 20 files changed, 27 insertions(+), 21 deletions(-) diff --git a/.bingo/Variables.mk b/.bingo/Variables.mk index 6096ca9e3..43c811415 100644 --- a/.bingo/Variables.mk +++ b/.bingo/Variables.mk @@ -65,11 +65,11 @@ $(HUGO): $(BINGO_DIR)/hugo.mod @echo "(re)installing $(GOBIN)/hugo-v0.115.2" @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=hugo.mod -o=$(GOBIN)/hugo-v0.115.2 "github.com/gohugoio/hugo" -MOCKERY := $(GOBIN)/mockery-v2.40.1 +MOCKERY := $(GOBIN)/mockery-v2.40.2 $(MOCKERY): $(BINGO_DIR)/mockery.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/mockery-v2.40.1" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=mockery.mod -o=$(GOBIN)/mockery-v2.40.1 "github.com/vektra/mockery/v2" + @echo "(re)installing $(GOBIN)/mockery-v2.40.2" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=mockery.mod -o=$(GOBIN)/mockery-v2.40.2 "github.com/vektra/mockery/v2" MUTAGEN := $(GOBIN)/mutagen-v0.13.1 $(MUTAGEN): $(BINGO_DIR)/mutagen.mod diff --git a/.bingo/mockery.mod b/.bingo/mockery.mod index 9c2314f8e..6dd4aa4be 100644 --- a/.bingo/mockery.mod +++ b/.bingo/mockery.mod @@ -2,4 +2,4 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT go 1.21.6 -require github.com/vektra/mockery/v2 v2.40.1 +require github.com/vektra/mockery/v2 v2.40.2 diff --git a/.bingo/mockery.sum b/.bingo/mockery.sum index e06a7204a..f9acfc356 100644 --- a/.bingo/mockery.sum +++ b/.bingo/mockery.sum @@ -190,6 +190,8 @@ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8 github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/vektra/mockery/v2 v2.40.1 h1:8D01rBqloDLDHKZGXkyUD9Yj5Z+oDXBqDZ+tRXYM/oA= github.com/vektra/mockery/v2 v2.40.1/go.mod h1:dPzGtjT0/Uu4hqpF6QNHwz+GLago7lq1bxdj9wHbGKo= +github.com/vektra/mockery/v2 v2.40.2 h1:JKuQ74IXibMCGKG6F0yvH/s2sNs1CHy/nGBP0We5DJ8= +github.com/vektra/mockery/v2 v2.40.2/go.mod h1:KYBZF/7sqOa86BaOZPYsoCZWEWLS90a5oBLg2pVudxY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -242,6 +244,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -395,6 +399,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/.bingo/variables.env b/.bingo/variables.env index a4673819c..7f7a6eede 100644 --- a/.bingo/variables.env +++ b/.bingo/variables.env @@ -24,7 +24,7 @@ GOVULNCHECK="${GOBIN}/govulncheck-v1.0.1" HUGO="${GOBIN}/hugo-v0.115.2" -MOCKERY="${GOBIN}/mockery-v2.40.1" +MOCKERY="${GOBIN}/mockery-v2.40.2" MUTAGEN="${GOBIN}/mutagen-v0.13.1" diff --git a/ocis-pkg/oidc/mocks/oidc_client.go b/ocis-pkg/oidc/mocks/oidc_client.go index 5a8223973..0aa0dfdfe 100644 --- a/ocis-pkg/oidc/mocks/oidc_client.go +++ b/ocis-pkg/oidc/mocks/oidc_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/graph/mocks/http_client.go b/services/graph/mocks/http_client.go index 6baaf3319..b31a16b26 100644 --- a/services/graph/mocks/http_client.go +++ b/services/graph/mocks/http_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/graph/mocks/permissions.go b/services/graph/mocks/permissions.go index 73bf9421d..a0645ce95 100644 --- a/services/graph/mocks/permissions.go +++ b/services/graph/mocks/permissions.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/graph/mocks/publisher.go b/services/graph/mocks/publisher.go index 189ebe218..7449caf0f 100644 --- a/services/graph/mocks/publisher.go +++ b/services/graph/mocks/publisher.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/graph/mocks/role_service.go b/services/graph/mocks/role_service.go index 70fadd24e..4538a9430 100644 --- a/services/graph/mocks/role_service.go +++ b/services/graph/mocks/role_service.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/graph/pkg/identity/mocks/backend.go b/services/graph/pkg/identity/mocks/backend.go index 885ee880a..4a6153290 100644 --- a/services/graph/pkg/identity/mocks/backend.go +++ b/services/graph/pkg/identity/mocks/backend.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/graph/pkg/identity/mocks/education_backend.go b/services/graph/pkg/identity/mocks/education_backend.go index 44e831c58..8799d04ad 100644 --- a/services/graph/pkg/identity/mocks/education_backend.go +++ b/services/graph/pkg/identity/mocks/education_backend.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/graph/pkg/identity/mocks/ldapclient.go b/services/graph/pkg/identity/mocks/ldapclient.go index f9f1e15b6..c4dba72e1 100644 --- a/services/graph/pkg/identity/mocks/ldapclient.go +++ b/services/graph/pkg/identity/mocks/ldapclient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/invitations/pkg/backends/keycloak/mocks/client.go b/services/invitations/pkg/backends/keycloak/mocks/client.go index be451669b..c6bdfbc7c 100644 --- a/services/invitations/pkg/backends/keycloak/mocks/client.go +++ b/services/invitations/pkg/backends/keycloak/mocks/client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/proxy/pkg/user/backend/mocks/user_backend.go b/services/proxy/pkg/user/backend/mocks/user_backend.go index c625438af..2d57b3401 100644 --- a/services/proxy/pkg/user/backend/mocks/user_backend.go +++ b/services/proxy/pkg/user/backend/mocks/user_backend.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/proxy/pkg/userroles/mocks/user_role_assigner.go b/services/proxy/pkg/userroles/mocks/user_role_assigner.go index 3a601b6aa..babeb5491 100644 --- a/services/proxy/pkg/userroles/mocks/user_role_assigner.go +++ b/services/proxy/pkg/userroles/mocks/user_role_assigner.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/search/pkg/content/mocks/extractor.go b/services/search/pkg/content/mocks/extractor.go index 80a795af3..273d96b01 100644 --- a/services/search/pkg/content/mocks/extractor.go +++ b/services/search/pkg/content/mocks/extractor.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/search/pkg/content/mocks/retriever.go b/services/search/pkg/content/mocks/retriever.go index 76271c220..c755da70e 100644 --- a/services/search/pkg/content/mocks/retriever.go +++ b/services/search/pkg/content/mocks/retriever.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/search/pkg/engine/mocks/engine.go b/services/search/pkg/engine/mocks/engine.go index aca2f730f..d1c356d0c 100644 --- a/services/search/pkg/engine/mocks/engine.go +++ b/services/search/pkg/engine/mocks/engine.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/search/pkg/search/mocks/searcher.go b/services/search/pkg/search/mocks/searcher.go index 8900f1dc2..cb0b0b529 100644 --- a/services/search/pkg/search/mocks/searcher.go +++ b/services/search/pkg/search/mocks/searcher.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks diff --git a/services/settings/pkg/settings/mocks/manager.go b/services/settings/pkg/settings/mocks/manager.go index 687d16888..b4d6b43c6 100644 --- a/services/settings/pkg/settings/mocks/manager.go +++ b/services/settings/pkg/settings/mocks/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.40.1. DO NOT EDIT. +// Code generated by mockery v2.40.2. DO NOT EDIT. package mocks From 341c55f17893924b1c90e447d1a5e5772200a78e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 06:17:51 +0000 Subject: [PATCH 073/112] build(deps): bump golang.org/x/oauth2 from 0.16.0 to 0.17.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.16.0 to 0.17.0. - [Commits](https://github.com/golang/oauth2/compare/v0.16.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- vendor/modules.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index d7188933c..ca7948872 100644 --- a/go.mod +++ b/go.mod @@ -97,7 +97,7 @@ require ( golang.org/x/crypto v0.19.0 golang.org/x/image v0.15.0 golang.org/x/net v0.21.0 - golang.org/x/oauth2 v0.16.0 + golang.org/x/oauth2 v0.17.0 golang.org/x/sync v0.6.0 golang.org/x/term v0.17.0 golang.org/x/text v0.14.0 diff --git a/go.sum b/go.sum index 3b62591e1..118628d84 100644 --- a/go.sum +++ b/go.sum @@ -2349,8 +2349,8 @@ golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4 golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/vendor/modules.txt b/vendor/modules.txt index 77193ee9f..511434e5b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2090,7 +2090,7 @@ golang.org/x/net/netutil golang.org/x/net/proxy golang.org/x/net/publicsuffix golang.org/x/net/trace -# golang.org/x/oauth2 v0.16.0 +# golang.org/x/oauth2 v0.17.0 ## explicit; go 1.18 golang.org/x/oauth2 golang.org/x/oauth2/internal From 7c52ad6c25515610de708846c22bade94db69c8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 06:18:05 +0000 Subject: [PATCH 074/112] build(deps): bump go.opentelemetry.io/contrib/zpages Bumps [go.opentelemetry.io/contrib/zpages](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.47.0 to 0.48.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.47.0...zpages/v0.48.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/contrib/zpages dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- vendor/go.opentelemetry.io/contrib/zpages/version.go | 2 +- vendor/modules.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index ca7948872..8146eee4d 100644 --- a/go.mod +++ b/go.mod @@ -88,7 +88,7 @@ require ( go-micro.dev/v4 v4.9.0 go.etcd.io/bbolt v1.3.8 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 - go.opentelemetry.io/contrib/zpages v0.47.0 + go.opentelemetry.io/contrib/zpages v0.48.0 go.opentelemetry.io/otel v1.23.1 go.opentelemetry.io/otel/exporters/jaeger v1.17.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 diff --git a/go.sum b/go.sum index 118628d84..96d01b250 100644 --- a/go.sum +++ b/go.sum @@ -2096,8 +2096,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= -go.opentelemetry.io/contrib/zpages v0.47.0 h1:ekpdNa2wqOvAfwZIGDIIV02zmR+z08aWPt21KrPJnaU= -go.opentelemetry.io/contrib/zpages v0.47.0/go.mod h1:rBeFA/UxnMjRlEGpmClIqzf1mCIKtl7ahjww3wsSdGs= +go.opentelemetry.io/contrib/zpages v0.48.0 h1:7vnqarYRu2VYvUUwrLWXRk5HqPuCbtuZdgR4eaVeP3Y= +go.opentelemetry.io/contrib/zpages v0.48.0/go.mod h1:fzewgBzh126TFUgnNhI9/1hjvnZes7wBKXAMQmyM01g= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= diff --git a/vendor/go.opentelemetry.io/contrib/zpages/version.go b/vendor/go.opentelemetry.io/contrib/zpages/version.go index 5f3c6dd81..00dfb0ce6 100644 --- a/vendor/go.opentelemetry.io/contrib/zpages/version.go +++ b/vendor/go.opentelemetry.io/contrib/zpages/version.go @@ -16,7 +16,7 @@ package zpages // import "go.opentelemetry.io/contrib/zpages" // Version is the current release version of the zpages span processor. func Version() string { - return "0.47.0" + return "0.48.0" // This string is updated by the pre_release.sh script during release } diff --git a/vendor/modules.txt b/vendor/modules.txt index 511434e5b..074155c39 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1938,7 +1938,7 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/inte ## explicit; go 1.20 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil -# go.opentelemetry.io/contrib/zpages v0.47.0 +# go.opentelemetry.io/contrib/zpages v0.48.0 ## explicit; go 1.20 go.opentelemetry.io/contrib/zpages go.opentelemetry.io/contrib/zpages/internal From 58d6b837aee5128abfc39c248e48355d6eea41f2 Mon Sep 17 00:00:00 2001 From: Amrita <54478846+amrita-shrestha@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:10:51 +0545 Subject: [PATCH 075/112] try to validate json scheme stricity (#8376) --- .../apiSharingNg/listPermissions.feature | 72 +------------------ 1 file changed, 2 insertions(+), 70 deletions(-) diff --git a/tests/acceptance/features/apiSharingNg/listPermissions.feature b/tests/acceptance/features/apiSharingNg/listPermissions.feature index e15b85720..e2ee6aa77 100644 --- a/tests/acceptance/features/apiSharingNg/listPermissions.feature +++ b/tests/acceptance/features/apiSharingNg/listPermissions.feature @@ -48,6 +48,8 @@ Feature: List a sharing permissions }, "@libre.graph.permissions.roles.allowedValues": { "type": "array", + "minItems": 4, + "maxItems": 4, "items": [ { "type": "object", @@ -188,76 +190,6 @@ Feature: List a sharing permissions ] } } - }, - { - "type": "object", - "required": [ - "@libre.graph.weight", - "description", - "displayName", - "id" - ], - "properties": { - "@libre.graph.weight": { - "type": "integer", - "enum": [ - 5 - ] - }, - "description": { - "type": "string", - "enum": [ - "Grants co-owner permissions on a resource" - ] - }, - "displayName": { - "type": "string", - "enum": [ - "Co Owner" - ] - }, - "id": { - "type": "string", - "enum": [ - "3a4ba8e9-6a0d-4235-9140-0e7a34007abe" - ] - } - } - }, - { - "type": "object", - "required": [ - "@libre.graph.weight", - "description", - "displayName", - "id" - ], - "properties": { - "@libre.graph.weight": { - "type": "integer", - "enum": [ - 6 - ] - }, - "description": { - "type": "string", - "enum": [ - "Grants manager permissions on a resource. Semantically equivalent to co-owner" - ] - }, - "displayName": { - "type": "string", - "enum": [ - "Manager" - ] - }, - "id": { - "type": "string", - "enum": [ - "312c0871-5ef7-4b3a-85b6-0e4074c64049" - ] - } - } } ] } From a5d7c292db34654720c2bbd7fe8f463f9f4a05c7 Mon Sep 17 00:00:00 2001 From: Nalem7 <61624650+nabim777@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:47:37 +0545 Subject: [PATCH 076/112] skip spacecontext subsitutions for reva (#8422) --- .../features/bootstrap/FeatureContext.php | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/acceptance/features/bootstrap/FeatureContext.php b/tests/acceptance/features/bootstrap/FeatureContext.php index 8ac2409b4..0293466e0 100644 --- a/tests/acceptance/features/bootstrap/FeatureContext.php +++ b/tests/acceptance/features/bootstrap/FeatureContext.php @@ -2880,16 +2880,22 @@ class FeatureContext extends BehatVariablesContext { "function" => [$this, "getGroupIdByGroupName"], "parameter" => [$group] - ], - [ - "code" => "%shares_drive_id%", - "function" => [ - $this->spacesContext, - "getSpaceIdByName" - ], - "parameter" => [$user, "Shares"] ] ); + + if (!OcisHelper::isTestingOnReva()) { + array_push( + $substitutions, + [ + "code" => "%shares_drive_id%", + "function" => [ + $this->spacesContext, + "getSpaceIdByName" + ], + "parameter" => [$user, "Shares"] + ] + ); + } } if (!empty($additionalSubstitutions)) { From bc610b37a9d8b978682f461514837c6b17e5dfe9 Mon Sep 17 00:00:00 2001 From: kobergj Date: Fri, 9 Feb 2024 16:24:56 +0100 Subject: [PATCH 077/112] Improve Single Binary Boot time (#8320) * remove wait times on single binary boot Signed-off-by: jkoberg * rework service starting Signed-off-by: jkoberg --------- Signed-off-by: jkoberg --- changelog/unreleased/speed-up-starttime.md | 5 + ocis/pkg/runtime/service/service.go | 314 ++++++++++++--------- 2 files changed, 179 insertions(+), 140 deletions(-) create mode 100644 changelog/unreleased/speed-up-starttime.md diff --git a/changelog/unreleased/speed-up-starttime.md b/changelog/unreleased/speed-up-starttime.md new file mode 100644 index 000000000..15fa90526 --- /dev/null +++ b/changelog/unreleased/speed-up-starttime.md @@ -0,0 +1,5 @@ +Enhancement: Improve ocis single binary start + +Removes waiting times when starting the single binary. Improves ocis single binary boot time from 8s to 2.5s + +https://github.com/owncloud/ocis/pull/8320 diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index c1ef41747..7830bcde6 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -13,12 +13,17 @@ import ( "syscall" "time" + "github.com/cenkalti/backoff" + "github.com/cs3org/reva/v2/pkg/events/stream" + "github.com/cs3org/reva/v2/pkg/logger" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/mohae/deepcopy" "github.com/olekukonko/tablewriter" "github.com/thejerf/suture/v4" ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/log" + ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" "github.com/owncloud/ocis/v2/ocis-pkg/shared" antivirus "github.com/owncloud/ocis/v2/services/antivirus/pkg/command" appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/command" @@ -64,22 +69,19 @@ import ( var ( // runset keeps track of which services to start supervised. runset map[string]struct{} - // time to wait after starting the preliminary services - _preliminaryDelay = 6 * time.Second - // time to wait between starting service groups (preliminary, main, delayed) - _startDelay = 2 * time.Second + + // wait funcs run after the service group has been started. + _waitFuncs = []func(*ociscfg.Config) error{pingNats, pingGateway, nil, wait(time.Second), nil} ) type serviceFuncMap map[string]func(*ociscfg.Config) suture.Service // Service represents a RPC service. type Service struct { - Supervisor *suture.Supervisor - Preliminary serviceFuncMap - ServicesRegistry serviceFuncMap - Delayed serviceFuncMap - Additional serviceFuncMap - Log log.Logger + Supervisor *suture.Supervisor + Services []serviceFuncMap + Additional serviceFuncMap + Log log.Logger serviceToken map[string][]suture.ServiceToken context context.Context @@ -108,11 +110,9 @@ func NewService(options ...Option) (*Service, error) { globalCtx, cancelGlobal := context.WithCancel(context.Background()) s := &Service{ - Preliminary: make(serviceFuncMap), - ServicesRegistry: make(serviceFuncMap), - Delayed: make(serviceFuncMap), - Additional: make(serviceFuncMap), - Log: l, + Services: make([]serviceFuncMap, len(_waitFuncs)), + Additional: make(serviceFuncMap), + Log: l, serviceToken: make(map[string][]suture.ServiceToken), context: globalCtx, @@ -120,162 +120,200 @@ func NewService(options ...Option) (*Service, error) { cfg: opts.Config, } - // start nats first - it is used as service registry - s.Preliminary[opts.Config.Nats.Service.Name] = NewSutureServiceBuilder(func(ctx context.Context, cfg *ociscfg.Config) error { + // populate services + reg := func(priority int, name string, exec func(context.Context, *ociscfg.Config) error) { + if s.Services[priority] == nil { + s.Services[priority] = make(serviceFuncMap) + } + s.Services[priority][name] = NewSutureServiceBuilder(exec) + } + + // nats is in priority group 0. It needs to start before all other services + reg(0, opts.Config.Nats.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Nats.Context = ctx cfg.Nats.Commons = cfg.Commons return nats.Execute(cfg.Nats) }) - // populate services - reg := func(name string, exec func(context.Context, *ociscfg.Config) error) { - s.ServicesRegistry[name] = NewSutureServiceBuilder(exec) - } - reg(opts.Config.AppProvider.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.AppProvider.Context = ctx - cfg.AppProvider.Commons = cfg.Commons - return appProvider.Execute(cfg.AppProvider) - }) - reg(opts.Config.AppRegistry.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.AppRegistry.Context = ctx - cfg.AppRegistry.Commons = cfg.Commons - return appRegistry.Execute(cfg.AppRegistry) - }) - reg(opts.Config.AuthBasic.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.AuthBasic.Context = ctx - cfg.AuthBasic.Commons = cfg.Commons - return authbasic.Execute(cfg.AuthBasic) - }) - reg(opts.Config.AuthMachine.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.AuthMachine.Context = ctx - cfg.AuthMachine.Commons = cfg.Commons - return authmachine.Execute(cfg.AuthMachine) - }) - reg(opts.Config.AuthService.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.AuthService.Context = ctx - cfg.AuthService.Commons = cfg.Commons - return authservice.Execute(cfg.AuthService) - }) - reg(opts.Config.Clientlog.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.Clientlog.Context = ctx - cfg.Clientlog.Commons = cfg.Commons - return clientlog.Execute(cfg.Clientlog) - }) - reg(opts.Config.EventHistory.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.EventHistory.Context = ctx - cfg.EventHistory.Commons = cfg.Commons - return eventhistory.Execute(cfg.EventHistory) - }) - reg(opts.Config.Gateway.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + // gateway is in priority group 1. It needs to start before the reva services + reg(1, opts.Config.Gateway.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Gateway.Context = ctx cfg.Gateway.Commons = cfg.Commons return gateway.Execute(cfg.Gateway) }) - reg(opts.Config.Graph.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + + // priority group 2 is empty for now + + // most services are in priority group 3 + reg(3, opts.Config.AppProvider.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AppProvider.Context = ctx + cfg.AppProvider.Commons = cfg.Commons + return appProvider.Execute(cfg.AppProvider) + }) + reg(3, opts.Config.AppRegistry.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AppRegistry.Context = ctx + cfg.AppRegistry.Commons = cfg.Commons + return appRegistry.Execute(cfg.AppRegistry) + }) + reg(3, opts.Config.AuthBasic.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AuthBasic.Context = ctx + cfg.AuthBasic.Commons = cfg.Commons + return authbasic.Execute(cfg.AuthBasic) + }) + reg(3, opts.Config.AuthMachine.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AuthMachine.Context = ctx + cfg.AuthMachine.Commons = cfg.Commons + return authmachine.Execute(cfg.AuthMachine) + }) + reg(3, opts.Config.AuthService.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AuthService.Context = ctx + cfg.AuthService.Commons = cfg.Commons + return authservice.Execute(cfg.AuthService) + }) + reg(3, opts.Config.Clientlog.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Clientlog.Context = ctx + cfg.Clientlog.Commons = cfg.Commons + return clientlog.Execute(cfg.Clientlog) + }) + reg(3, opts.Config.EventHistory.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.EventHistory.Context = ctx + cfg.EventHistory.Commons = cfg.Commons + return eventhistory.Execute(cfg.EventHistory) + }) + reg(3, opts.Config.Graph.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Graph.Context = ctx cfg.Graph.Commons = cfg.Commons return graph.Execute(cfg.Graph) }) - reg(opts.Config.Groups.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Groups.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Groups.Context = ctx cfg.Groups.Commons = cfg.Commons return groups.Execute(cfg.Groups) }) - reg(opts.Config.IDM.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.IDM.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.IDM.Context = ctx cfg.IDM.Commons = cfg.Commons return idm.Execute(cfg.IDM) }) - reg(opts.Config.Invitations.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.Invitations.Context = ctx - cfg.Invitations.Commons = cfg.Commons - return invitations.Execute(cfg.Invitations) - }) - reg(opts.Config.Notifications.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Notifications.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Notifications.Context = ctx cfg.Notifications.Commons = cfg.Commons return notifications.Execute(cfg.Notifications) }) - reg(opts.Config.OCDav.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.OCDav.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.OCDav.Context = ctx cfg.OCDav.Commons = cfg.Commons return ocdav.Execute(cfg.OCDav) }) - reg(opts.Config.OCS.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.OCS.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.OCS.Context = ctx cfg.OCS.Commons = cfg.Commons return ocs.Execute(cfg.OCS) }) - reg(opts.Config.Postprocessing.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Postprocessing.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Postprocessing.Context = ctx cfg.Postprocessing.Commons = cfg.Commons return postprocessing.Execute(cfg.Postprocessing) }) - reg(opts.Config.Search.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Search.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Search.Context = ctx cfg.Search.Commons = cfg.Commons return search.Execute(cfg.Search) }) - reg(opts.Config.Settings.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Settings.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Settings.Context = ctx cfg.Settings.Commons = cfg.Commons return settings.Execute(cfg.Settings) }) - reg(opts.Config.StoragePublicLink.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.StoragePublicLink.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.StoragePublicLink.Context = ctx cfg.StoragePublicLink.Commons = cfg.Commons return storagepublic.Execute(cfg.StoragePublicLink) }) - reg(opts.Config.StorageShares.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.StorageShares.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.StorageShares.Context = ctx cfg.StorageShares.Commons = cfg.Commons return storageshares.Execute(cfg.StorageShares) }) - reg(opts.Config.StorageSystem.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.StorageSystem.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.StorageSystem.Context = ctx cfg.StorageSystem.Commons = cfg.Commons return storageSystem.Execute(cfg.StorageSystem) }) - reg(opts.Config.StorageUsers.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.StorageUsers.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.StorageUsers.Context = ctx cfg.StorageUsers.Commons = cfg.Commons return storageusers.Execute(cfg.StorageUsers) }) - reg(opts.Config.Store.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Store.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Store.Context = ctx cfg.Store.Commons = cfg.Commons return store.Execute(cfg.Store) }) - reg(opts.Config.Thumbnails.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Thumbnails.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Thumbnails.Context = ctx cfg.Thumbnails.Commons = cfg.Commons return thumbnails.Execute(cfg.Thumbnails) }) - reg(opts.Config.Userlog.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Userlog.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Userlog.Context = ctx cfg.Userlog.Commons = cfg.Commons return userlog.Execute(cfg.Userlog) }) - reg(opts.Config.Users.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Users.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Users.Context = ctx cfg.Users.Commons = cfg.Commons return users.Execute(cfg.Users) }) - reg(opts.Config.Web.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Web.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Web.Context = ctx cfg.Web.Commons = cfg.Commons return web.Execute(cfg.Web) }) - reg(opts.Config.WebDAV.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.WebDAV.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.WebDAV.Context = ctx cfg.WebDAV.Commons = cfg.Commons return webdav.Execute(cfg.WebDAV) }) - reg(opts.Config.Webfinger.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + reg(3, opts.Config.Webfinger.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Webfinger.Context = ctx cfg.Webfinger.Commons = cfg.Commons return webfinger.Execute(cfg.Webfinger) }) + reg(3, opts.Config.IDP.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.IDP.Context = ctx + cfg.IDP.Commons = cfg.Commons + return idp.Execute(cfg.IDP) + }) + reg(3, opts.Config.Proxy.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Proxy.Context = ctx + cfg.Proxy.Commons = cfg.Commons + return proxy.Execute(cfg.Proxy) + }) + reg(3, opts.Config.Sharing.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Sharing.Context = ctx + cfg.Sharing.Commons = cfg.Commons + return sharing.Execute(cfg.Sharing) + }) + reg(3, opts.Config.SSE.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.SSE.Context = ctx + cfg.SSE.Commons = cfg.Commons + return sse.Execute(cfg.SSE) + }) + reg(3, opts.Config.OCM.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.OCM.Context = ctx + cfg.OCM.Commons = cfg.Commons + return ocm.Execute(cfg.OCM) + }) + + // out of some unknown reason ci gets angry when frontend service starts in priority group 3 + // this is not reproducible locally, it can start when nats and gateway are already running + // FIXME: find out why + reg(4, opts.Config.Frontend.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Frontend.Context = ctx + cfg.Frontend.Commons = cfg.Commons + return frontend.Execute(cfg.Frontend) + }) // populate optional services areg := func(name string, exec func(context.Context, *ociscfg.Config) error) { @@ -296,40 +334,10 @@ func NewService(options ...Option) (*Service, error) { cfg.Policies.Commons = cfg.Commons return policies.Execute(cfg.Policies) }) - - // populate delayed services - dreg := func(name string, exec func(context.Context, *ociscfg.Config) error) { - s.Delayed[name] = NewSutureServiceBuilder(exec) - } - dreg(opts.Config.Frontend.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.Frontend.Context = ctx - cfg.Frontend.Commons = cfg.Commons - return frontend.Execute(cfg.Frontend) - }) - dreg(opts.Config.IDP.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.IDP.Context = ctx - cfg.IDP.Commons = cfg.Commons - return idp.Execute(cfg.IDP) - }) - dreg(opts.Config.Proxy.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.Proxy.Context = ctx - cfg.Proxy.Commons = cfg.Commons - return proxy.Execute(cfg.Proxy) - }) - dreg(opts.Config.Sharing.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.Sharing.Context = ctx - cfg.Sharing.Commons = cfg.Commons - return sharing.Execute(cfg.Sharing) - }) - dreg(opts.Config.SSE.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.SSE.Context = ctx - cfg.SSE.Commons = cfg.Commons - return sse.Execute(cfg.SSE) - }) - dreg(opts.Config.OCM.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.OCM.Context = ctx - cfg.OCM.Commons = cfg.Commons - return ocm.Execute(cfg.OCM) + areg(opts.Config.Invitations.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.Invitations.Context = ctx + cfg.Invitations.Commons = cfg.Commons + return invitations.Execute(cfg.Invitations) }) return s, nil @@ -378,14 +386,14 @@ func Start(o ...Option) error { if err = rpc.Register(s); err != nil { if s != nil { - s.Log.Fatal().Err(err) + s.Log.Fatal().Err(err).Msg("could not register rpc service") } } rpc.HandleHTTP() l, err := net.Listen("tcp", net.JoinHostPort(s.cfg.Runtime.Host, s.cfg.Runtime.Port)) if err != nil { - s.Log.Fatal().Err(err) + s.Log.Fatal().Err(err).Msg("could not start listener") } defer func() { @@ -402,9 +410,6 @@ func Start(o ...Option) error { // prepare the set of services to run s.generateRunSet(s.cfg) - // schedule preliminary services first - scheduleServiceTokens(s, s.Preliminary) - // there are reasons not to do this, but we have race conditions ourselves. Until we resolve them, mind the following disclaimer: // Calling ServeBackground will CORRECTLY start the supervisor running in a new goroutine. It is risky to directly run // go supervisor.Serve() @@ -415,19 +420,18 @@ func Start(o ...Option) error { // trap will block on halt channel for interruptions. go trap(s, halt) - // grace period for preliminary services to get up - time.Sleep(_preliminaryDelay) - - // schedule services that we are sure don't have interdependencies. - scheduleServiceTokens(s, s.ServicesRegistry) + for i, service := range s.Services { + scheduleServiceTokens(s, service) + if _waitFuncs[i] != nil { + if err := _waitFuncs[i](s.cfg); err != nil { + s.Log.Fatal().Err(err).Msg("wait func failed") + } + } + } // schedule services that are optional scheduleServiceTokens(s, s.Additional) - // add services with delayed execution. - time.Sleep(_startDelay) - scheduleServiceTokens(s, s.Delayed) - return http.Serve(l, nil) } @@ -454,16 +458,10 @@ func (s *Service) generateRunSet(cfg *ociscfg.Config) { return } - for name := range s.Preliminary { - runset[name] = struct{}{} - } - - for name := range s.ServicesRegistry { - runset[name] = struct{}{} - } - - for name := range s.Delayed { - runset[name] = struct{}{} + for _, service := range s.Services { + for name := range service { + runset[name] = struct{}{} + } } // add additional services if explicitly added by config @@ -516,3 +514,39 @@ func trap(s *Service, halt chan os.Signal) { s.Log.Debug().Str("service", "runtime service").Msgf("terminating with signal: %v", s) os.Exit(0) } + +// pingNats will attempt to connect to nats, blocking until a connection is established +func pingNats(cfg *ociscfg.Config) error { + // We need to get a natsconfig from somewhere. We can use any one. + evcfg := cfg.Postprocessing.Postprocessing.Events + _, err := stream.NatsFromConfig("initial", true, stream.NatsConfig(evcfg)) + return err +} + +func pingGateway(_ *ociscfg.Config) error { + // init grpc connection + _, err := ogrpc.NewClient() + if err != nil { + return err + } + + b := backoff.NewExponentialBackOff() + o := func() error { + n := b.NextBackOff() + _, err := pool.GetGatewayServiceClient("com.owncloud.api.gateway") + if err != nil && n > time.Second { + logger.New().Error().Err(err).Msgf("can't connect to gateway service, retrying in %s", n) + } + return err + } + + err = backoff.Retry(o, b) + return err +} + +func wait(d time.Duration) func(cfg *ociscfg.Config) error { + return func(cfg *ociscfg.Config) error { + time.Sleep(d) + return nil + } +} From 5e94c7281d2b8fe0405ff28f54ceb11359a88092 Mon Sep 17 00:00:00 2001 From: kobergj Date: Fri, 9 Feb 2024 15:25:36 +0000 Subject: [PATCH 078/112] Automated changelog update [skip ci] --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5f22dc93..e8fbee2ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ The following sections list the changes for unreleased. * Enhancement - Update reva to latest edge version: [#8287](https://github.com/owncloud/ocis/pull/8287) * Enhancement - Allow restarting multiple uploads with one command: [#8287](https://github.com/owncloud/ocis/pull/8287) * Enhancement - Modify the concurrency default: [#8309](https://github.com/owncloud/ocis/pull/8309) +* Enhancement - Improve ocis single binary start: [#8320](https://github.com/owncloud/ocis/pull/8320) * Enhancement - Bump reva: [#8340](https://github.com/owncloud/ocis/pull/8340) * Enhancement - Allow sending multiple user ids in one sse event: [#8379](https://github.com/owncloud/ocis/pull/8379) * Enhancement - Allow to skip service listing: [#8408](https://github.com/owncloud/ocis/pull/8408) @@ -357,6 +358,13 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/8309 https://github.com/cs3org/reva/pull/4485 +* Enhancement - Improve ocis single binary start: [#8320](https://github.com/owncloud/ocis/pull/8320) + + Removes waiting times when starting the single binary. Improves ocis single + binary boot time from 8s to 2.5s + + https://github.com/owncloud/ocis/pull/8320 + * Enhancement - Bump reva: [#8340](https://github.com/owncloud/ocis/pull/8340) We have bumped reve to pull in the changes needed for automatically increment From 00e43b82742bd0f70b742e559ba785ab563ed804 Mon Sep 17 00:00:00 2001 From: Michael Barz Date: Thu, 1 Feb 2024 12:24:30 +0100 Subject: [PATCH 079/112] feat: use env variable in yaml config files --- changelog/unreleased/enhancement-env-in-yaml.md | 15 +++++++++++++++ ocis-pkg/config/helpers.go | 1 + 2 files changed, 16 insertions(+) create mode 100644 changelog/unreleased/enhancement-env-in-yaml.md diff --git a/changelog/unreleased/enhancement-env-in-yaml.md b/changelog/unreleased/enhancement-env-in-yaml.md new file mode 100644 index 000000000..29cef7756 --- /dev/null +++ b/changelog/unreleased/enhancement-env-in-yaml.md @@ -0,0 +1,15 @@ +Enhancement: Use environment variables in yaml config files + +We added the ability to use environment variables in yaml config files. This allows to use environment variables in the config files of the ocis services which will be replaced by the actual value of the environment variable at runtime. + +Example: + +``` +web: + http: + addr: ${SOME_HTTP_ADDR} +``` + +This makes it possible to use the same config file for different environments without the need to change the config file itself. This is especially useful when using docker-compose to run the ocis services. It is a common pattern to create an .env file which contains the environment variables for the docker-compose file. Now you can use the same .env file to configure the ocis services. + +https://github.com/owncloud/ocis/pull/8339 diff --git a/ocis-pkg/config/helpers.go b/ocis-pkg/config/helpers.go index 7b72333e9..c0ec6f1f4 100644 --- a/ocis-pkg/config/helpers.go +++ b/ocis-pkg/config/helpers.go @@ -20,6 +20,7 @@ var ( func BindSourcesToStructs(service string, dst interface{}) (*gofig.Config, error) { cnf := gofig.NewWithOptions(service) cnf.WithOptions(func(options *gofig.Options) { + options.ParseEnv = true options.DecoderConfig.TagName = decoderConfigTagName }) cnf.AddDriver(gooyaml.Driver) From e5d4d0291e60140dcb0955fb3d6e4356231edda1 Mon Sep 17 00:00:00 2001 From: Michael Barz Date: Sat, 10 Feb 2024 10:09:36 +0000 Subject: [PATCH 080/112] Automated changelog update [skip ci] --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8fbee2ff..cc6458ad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ The following sections list the changes for unreleased. * Enhancement - Allow restarting multiple uploads with one command: [#8287](https://github.com/owncloud/ocis/pull/8287) * Enhancement - Modify the concurrency default: [#8309](https://github.com/owncloud/ocis/pull/8309) * Enhancement - Improve ocis single binary start: [#8320](https://github.com/owncloud/ocis/pull/8320) +* Enhancement - Use environment variables in yaml config files: [#8339](https://github.com/owncloud/ocis/pull/8339) * Enhancement - Bump reva: [#8340](https://github.com/owncloud/ocis/pull/8340) * Enhancement - Allow sending multiple user ids in one sse event: [#8379](https://github.com/owncloud/ocis/pull/8379) * Enhancement - Allow to skip service listing: [#8408](https://github.com/owncloud/ocis/pull/8408) @@ -365,6 +366,30 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/8320 +* Enhancement - Use environment variables in yaml config files: [#8339](https://github.com/owncloud/ocis/pull/8339) + + We added the ability to use environment variables in yaml config files. This + allows to use environment variables in the config files of the ocis services + which will be replaced by the actual value of the environment variable at + runtime. + + Example: + + ``` + web: + http: + addr: ${SOME_HTTP_ADDR} + ``` + + This makes it possible to use the same config file for different environments + without the need to change the config file itself. This is especially useful + when using docker-compose to run the ocis services. It is a common pattern to + create an .env file which contains the environment variables for the + docker-compose file. Now you can use the same .env file to configure the ocis + services. + + https://github.com/owncloud/ocis/pull/8339 + * Enhancement - Bump reva: [#8340](https://github.com/owncloud/ocis/pull/8340) We have bumped reve to pull in the changes needed for automatically increment From 75a2ef639051d5d19c9e0cf7586017ccd389b759 Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Mon, 12 Feb 2024 11:07:21 +0100 Subject: [PATCH 081/112] fix: changelog (#8433) --- changelog/5.0.0-rc.1_2023-12-27/enhancement-sse-messaging.md | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog/5.0.0-rc.1_2023-12-27/enhancement-sse-messaging.md b/changelog/5.0.0-rc.1_2023-12-27/enhancement-sse-messaging.md index 31c98eb2a..e43383b85 100644 --- a/changelog/5.0.0-rc.1_2023-12-27/enhancement-sse-messaging.md +++ b/changelog/5.0.0-rc.1_2023-12-27/enhancement-sse-messaging.md @@ -5,6 +5,5 @@ In order to be able to send more content to the client, we have moved the endpoi * notify postprocessing state changes. * notify file locking and unlocking. -* ... @toDo https://github.com/owncloud/ocis/pull/6992 From 6a4832a6f8ec9b76b2d74a5dc9f023f58023b259 Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Mon, 12 Feb 2024 10:08:11 +0000 Subject: [PATCH 082/112] Automated changelog update [skip ci] --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc6458ad5..4e0b04d93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -864,7 +864,6 @@ The following sections list the changes for 5.0.0-rc.1. * notify postprocessing state changes. * notify file locking and unlocking. - * ... @toDo https://github.com/owncloud/ocis/pull/6992 From f746f4d1c19b493a5df3fc6f1cbc428c5ad2a00d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 06:12:51 +0000 Subject: [PATCH 083/112] build(deps): bump github.com/go-playground/validator/v10 Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.17.0 to 10.18.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.17.0...v10.18.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +- go.sum | 8 +- .../go-playground/validator/v10/Makefile | 2 +- .../go-playground/validator/v10/README.md | 2 +- vendor/github.com/leodido/go-urn/.gitignore | 3 +- vendor/github.com/leodido/go-urn/README.md | 126 +- vendor/github.com/leodido/go-urn/kind.go | 10 + vendor/github.com/leodido/go-urn/machine.go | 6292 +++++++++++++---- .../github.com/leodido/go-urn/machine.go.rl | 285 +- vendor/github.com/leodido/go-urn/makefile | 20 +- vendor/github.com/leodido/go-urn/options.go | 9 + .../github.com/leodido/go-urn/parsing_mode.go | 12 + vendor/github.com/leodido/go-urn/scim.go | 48 + .../leodido/go-urn/scim/schema/type.go | 36 + vendor/github.com/leodido/go-urn/urn.go | 75 +- vendor/github.com/leodido/go-urn/urn8141.go | 30 + vendor/modules.txt | 7 +- 17 files changed, 5414 insertions(+), 1557 deletions(-) create mode 100644 vendor/github.com/leodido/go-urn/kind.go create mode 100644 vendor/github.com/leodido/go-urn/options.go create mode 100644 vendor/github.com/leodido/go-urn/parsing_mode.go create mode 100644 vendor/github.com/leodido/go-urn/scim.go create mode 100644 vendor/github.com/leodido/go-urn/scim/schema/type.go create mode 100644 vendor/github.com/leodido/go-urn/urn8141.go diff --git a/go.mod b/go.mod index 8146eee4d..9888c1928 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/Nerzal/gocloak/v13 v13.9.0 github.com/bbalet/stopwords v1.0.0 github.com/blevesearch/bleve/v2 v2.3.10 + github.com/cenkalti/backoff v2.2.1+incompatible github.com/coreos/go-oidc/v3 v3.9.0 github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 github.com/cs3org/reva/v2 v2.18.1-0.20240208105019-d5e420d294be @@ -39,7 +40,7 @@ require ( github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0 github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0 github.com/go-ozzo/ozzo-validation/v4 v4.3.0 - github.com/go-playground/validator/v10 v10.17.0 + github.com/go-playground/validator/v10 v10.18.0 github.com/gofrs/uuid v4.4.0+incompatible github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/protobuf v1.5.3 @@ -152,7 +153,6 @@ require ( github.com/bluele/gcache v0.0.2 // indirect github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f // indirect github.com/bombsimon/logrusr/v3 v3.1.0 // indirect - github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/ceph/go-ceph v0.18.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -251,7 +251,7 @@ require ( github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.17.5 // indirect github.com/klauspost/cpuid/v2 v2.1.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/libregraph/oidc-go v1.0.0 // indirect github.com/longsleep/go-metrics v1.0.0 // indirect github.com/longsleep/rndm v1.2.0 // indirect diff --git a/go.sum b/go.sum index 96d01b250..152eab8e8 100644 --- a/go.sum +++ b/go.sum @@ -1236,8 +1236,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= -github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= +github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= @@ -1617,8 +1617,8 @@ github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HK github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1 h1:k56sFOOJ0CYuQtGoRSeAMhP1R692+iNH+S1dC/CEz0w= github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= github.com/libregraph/idm v0.4.1-0.20231213140724-56a222fb4215 h1:Yw/I6l/0S/zDq2Hnibvwy8cVLpMaBwDe0aUSv/FNU6U= diff --git a/vendor/github.com/go-playground/validator/v10/Makefile b/vendor/github.com/go-playground/validator/v10/Makefile index 09f171ba1..8bf4f28ab 100644 --- a/vendor/github.com/go-playground/validator/v10/Makefile +++ b/vendor/github.com/go-playground/validator/v10/Makefile @@ -1,4 +1,4 @@ -GOCMD=GO111MODULE=on go +GOCMD=go linters-install: @golangci-lint --version >/dev/null 2>&1 || { \ diff --git a/vendor/github.com/go-playground/validator/v10/README.md b/vendor/github.com/go-playground/validator/v10/README.md index 810097b3a..b3e113a19 100644 --- a/vendor/github.com/go-playground/validator/v10/README.md +++ b/vendor/github.com/go-playground/validator/v10/README.md @@ -1,7 +1,7 @@ Package validator ================= [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-10.17.0-green.svg) +![Project status](https://img.shields.io/badge/version-10.18.0-green.svg) [![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) diff --git a/vendor/github.com/leodido/go-urn/.gitignore b/vendor/github.com/leodido/go-urn/.gitignore index 89d4bc55d..427454f8f 100644 --- a/vendor/github.com/leodido/go-urn/.gitignore +++ b/vendor/github.com/leodido/go-urn/.gitignore @@ -9,4 +9,5 @@ *.txt vendor/ -/removecomments \ No newline at end of file +/removecomments +/snake2camel \ No newline at end of file diff --git a/vendor/github.com/leodido/go-urn/README.md b/vendor/github.com/leodido/go-urn/README.md index 731eecbb5..619475bfb 100644 --- a/vendor/github.com/leodido/go-urn/README.md +++ b/vendor/github.com/leodido/go-urn/README.md @@ -2,21 +2,34 @@ **A parser for URNs**. -> As seen on [RFC 2141](https://tools.ietf.org/html/rfc2141#ref-1). +> As seen on [RFC 2141](https://datatracker.ietf.org/doc/html/rfc2141), [RFC 7643](https://datatracker.ietf.org/doc/html/rfc7643#section-10), and on [RFC 8141](https://datatracker.ietf.org/doc/html/rfc8141). [API documentation](https://godoc.org/github.com/leodido/go-urn). +Starting with version 1.3 this library also supports [RFC 7643 SCIM URNs](https://datatracker.ietf.org/doc/html/rfc7643#section-10). + +Starting with version 1.4 this library also supports [RFC 8141 URNs (2017)](https://datatracker.ietf.org/doc/html/rfc8141). + ## Installation ``` go get github.com/leodido/go-urn ``` +## Features + +1. RFC 2141 URNs parsing (default) +2. RFC 8141 URNs parsing (supersedes RFC 2141) +3. RFC 7643 SCIM URNs parsing +4. Normalization as per RFCs +5. Lexical equivalence as per RFCs +6. Precise, fine-grained errors + ## Performances This implementation results to be really fast. -Usually below ½ microsecond on my machine[1](#mymachine). +Usually below 400 ns on my machine[1](#mymachine). Notice it also performs, while parsing: @@ -24,35 +37,36 @@ Notice it also performs, while parsing: 2. specific-string normalization ``` -ok/00/urn:a:b______________________________________/-4 20000000 265 ns/op 182 B/op 6 allocs/op -ok/01/URN:foo:a123,456_____________________________/-4 30000000 296 ns/op 200 B/op 6 allocs/op -ok/02/urn:foo:a123%2c456___________________________/-4 20000000 331 ns/op 208 B/op 6 allocs/op -ok/03/urn:ietf:params:scim:schemas:core:2.0:User___/-4 20000000 430 ns/op 280 B/op 6 allocs/op -ok/04/urn:ietf:params:scim:schemas:extension:enterp/-4 20000000 411 ns/op 312 B/op 6 allocs/op -ok/05/urn:ietf:params:scim:schemas:extension:enterp/-4 20000000 472 ns/op 344 B/op 6 allocs/op -ok/06/urn:burnout:nss______________________________/-4 30000000 257 ns/op 192 B/op 6 allocs/op -ok/07/urn:abcdefghilmnopqrstuvzabcdefghilm:x_______/-4 20000000 375 ns/op 213 B/op 6 allocs/op -ok/08/urn:urnurnurn:urn____________________________/-4 30000000 265 ns/op 197 B/op 6 allocs/op -ok/09/urn:ciao:@!=%2c(xyz)+a,b.*@g=$_'_____________/-4 20000000 307 ns/op 248 B/op 6 allocs/op -ok/10/URN:x:abc%1dz%2f%3az_________________________/-4 30000000 259 ns/op 212 B/op 6 allocs/op -no/11/URN:-xxx:x___________________________________/-4 20000000 445 ns/op 320 B/op 6 allocs/op -no/12/urn::colon:nss_______________________________/-4 20000000 461 ns/op 320 B/op 6 allocs/op -no/13/urn:abcdefghilmnopqrstuvzabcdefghilmn:specifi/-4 10000000 660 ns/op 320 B/op 6 allocs/op -no/14/URN:a!?:x____________________________________/-4 20000000 507 ns/op 320 B/op 6 allocs/op -no/15/urn:urn:NSS__________________________________/-4 20000000 429 ns/op 288 B/op 6 allocs/op -no/16/urn:white_space:NSS__________________________/-4 20000000 482 ns/op 320 B/op 6 allocs/op -no/17/urn:concat:no_spaces_________________________/-4 20000000 539 ns/op 328 B/op 7 allocs/op -no/18/urn:a:/______________________________________/-4 20000000 470 ns/op 320 B/op 7 allocs/op -no/19/urn:UrN:NSS__________________________________/-4 20000000 399 ns/op 288 B/op 6 allocs/op +ok/00/urn:a:b______________________________________/-10 51372006 109.0 ns/op 275 B/op 3 allocs/op +ok/01/URN:foo:a123,456_____________________________/-10 36024072 160.8 ns/op 296 B/op 6 allocs/op +ok/02/urn:foo:a123%2C456___________________________/-10 31901007 188.4 ns/op 320 B/op 7 allocs/op +ok/03/urn:ietf:params:scim:schemas:core:2.0:User___/-10 22736756 266.6 ns/op 376 B/op 6 allocs/op +ok/04/urn:ietf:params:scim:schemas:extension:enterp/-10 18291859 335.2 ns/op 408 B/op 6 allocs/op +ok/05/urn:ietf:params:scim:schemas:extension:enterp/-10 15283087 379.4 ns/op 440 B/op 6 allocs/op +ok/06/urn:burnout:nss______________________________/-10 39407593 155.1 ns/op 288 B/op 6 allocs/op +ok/07/urn:abcdefghilmnopqrstuvzabcdefghilm:x_______/-10 27832718 211.4 ns/op 307 B/op 4 allocs/op +ok/08/urn:urnurnurn:urn____________________________/-10 33269596 168.1 ns/op 293 B/op 6 allocs/op +ok/09/urn:ciao:!!*_________________________________/-10 41100675 148.8 ns/op 288 B/op 6 allocs/op +ok/10/urn:ciao:=@__________________________________/-10 37214253 149.7 ns/op 284 B/op 6 allocs/op +ok/11/urn:ciao:@!=%2C(xyz)+a,b.*@g=$_'_____________/-10 26534240 229.8 ns/op 336 B/op 7 allocs/op +ok/12/URN:x:abc%1Dz%2F%3az_________________________/-10 28166396 211.8 ns/op 336 B/op 7 allocs/op +no/13/URN:---xxx:x_________________________________/-10 23635159 255.6 ns/op 419 B/op 5 allocs/op +no/14/urn::colon:nss_______________________________/-10 23594779 258.4 ns/op 419 B/op 5 allocs/op +no/15/URN:@,:x_____________________________________/-10 23742535 261.5 ns/op 419 B/op 5 allocs/op +no/16/URN:URN:NSS__________________________________/-10 27432714 223.3 ns/op 371 B/op 5 allocs/op +no/17/urn:UrN:NSS__________________________________/-10 26922117 224.9 ns/op 371 B/op 5 allocs/op +no/18/urn:a:%______________________________________/-10 24926733 224.6 ns/op 371 B/op 5 allocs/op +no/19/urn:urn:NSS__________________________________/-10 27652641 220.7 ns/op 371 B/op 5 allocs/op ``` ---- +* [1]: Apple M1 Pro -* [1]: Intel Core i7-7600U CPU @ 2.80GHz - ---- ## Example + +For more examples take a look at the [examples file](examples_test.go). + + ```go package main @@ -64,6 +78,35 @@ import ( func main() { var uid = "URN:foo:a123,456" + // Parse the input string as a RFC 2141 URN only + u, e := urn.NewMachine().Parse(uid) + if e != nil { + fmt.Errorf(err) + + return + } + + fmt.Println(u.ID) + fmt.Println(u.SS) + + // Output: + // foo + // a123,456 +} +``` + +```go +package main + +import ( + "fmt" + "github.com/leodido/go-urn" +) + +func main() { + var uid = "URN:foo:a123,456" + + // Parse the input string as a RFC 2141 URN only u, ok := urn.Parse([]byte(uid)) if !ok { panic("error parsing urn") @@ -78,4 +121,33 @@ func main() { } ``` -[![Analytics](https://ga-beacon.appspot.com/UA-49657176-1/go-urn?flat)](https://github.com/igrigorik/ga-beacon) \ No newline at end of file +```go +package main + +import ( + "fmt" + "github.com/leodido/go-urn" +) + +func main() { + input := "urn:ietf:params:scim:api:messages:2.0:ListResponse" + + // Parsing the input string as a RFC 7643 SCIM URN + u, ok := urn.Parse([]byte(input), urn.WithParsingMode(urn.RFC7643Only)) + if !ok { + panic("error parsing urn") + } + + fmt.Println(u.IsSCIM()) + scim := u.SCIM() + fmt.Println(scim.Type.String()) + fmt.Println(scim.Name) + fmt.Println(scim.Other) + + // Output: + // true + // api + // messages + // 2.0:ListResponse +} +``` \ No newline at end of file diff --git a/vendor/github.com/leodido/go-urn/kind.go b/vendor/github.com/leodido/go-urn/kind.go new file mode 100644 index 000000000..f5e140f0a --- /dev/null +++ b/vendor/github.com/leodido/go-urn/kind.go @@ -0,0 +1,10 @@ +package urn + +type Kind int + +const ( + NONE Kind = iota + RFC2141 + RFC7643 + RFC8141 +) diff --git a/vendor/github.com/leodido/go-urn/machine.go b/vendor/github.com/leodido/go-urn/machine.go index f8d57b412..aec1ba69c 100644 --- a/vendor/github.com/leodido/go-urn/machine.go +++ b/vendor/github.com/leodido/go-urn/machine.go @@ -2,27 +2,98 @@ package urn import ( "fmt" + + scimschema "github.com/leodido/go-urn/scim/schema" ) var ( - errPrefix = "expecting the prefix to be the \"urn\" string (whatever case) [col %d]" - errIdentifier = "expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col %d]" - errSpecificString = "expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col %d]" - errNoUrnWithinID = "expecting the identifier to not contain the \"urn\" reserved string [col %d]" - errHex = "expecting the specific string hex chars to be well-formed (%%alnum{2}) [col %d]" - errParse = "parsing error [col %d]" + errPrefix = "expecting the prefix to be the \"urn\" string (whatever case) [col %d]" + errIdentifier = "expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col %d]" + errSpecificString = "expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col %d]" + errNoUrnWithinID = "expecting the identifier to not contain the \"urn\" reserved string [col %d]" + errHex = "expecting the percent encoded chars to be well-formed (%%alnum{2}) [col %d]" + errSCIMNamespace = "expecing the SCIM namespace identifier (ietf:params:scim) [col %d]" + errSCIMType = "expecting a correct SCIM type (schemas, api, param) [col %d]" + errSCIMName = "expecting one or more alnum char in the SCIM name part [col %d]" + errSCIMOther = "expecting a well-formed other SCIM part [col %d]" + errSCIMOtherIncomplete = "expecting a not empty SCIM other part after colon [col %d]" + err8141InformalID = "informal URN namespace must be in the form urn-[1-9][0-9] [col %d]" + err8141SpecificString = "expecting the specific string to contain alnum, hex, or others ([~&()+,-.:=@;$_!*'] or [/?] not in first position) chars [col %d]" + err8141Identifier = "expecting the indentifier to be a string with (length 2 to 32 chars) containing alnum (or dashes) not starting or ending with a dash [col %d]" + err8141RComponentStart = "expecting only one r-component (starting with the ?+ sequence) [col %d]" + err8141QComponentStart = "expecting only one q-component (starting with the ?= sequence) [col %d]" + err8141MalformedRComp = "expecting a non-empty r-component containing alnum, hex, or others ([~&()+,-.:=@;$_!*'] or [/?] but not at its beginning) [col %d]" + err8141MalformedQComp = "expecting a non-empty q-component containing alnum, hex, or others ([~&()+,-.:=@;$_!*'] or [/?] but not at its beginning) [col %d]" ) +var _toStateActions []byte = []byte{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 33, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, +} + +var _eofActions []byte = []byte{ + 0, 1, 1, 1, 1, 4, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 8, 9, + 9, 4, 4, 11, 1, 1, 1, 1, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 14, 14, 14, 14, 16, 18, 20, + 20, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 1, 1, 1, 1, 21, + 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, + 23, 24, 24, 25, 25, 0, 26, 28, + 28, 29, 29, 30, 30, 26, 26, 31, + 31, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 21, + 21, 22, 22, 22, 34, 34, 35, 37, + 37, 38, 40, 41, 41, 38, 42, 42, + 42, 44, 42, 48, 48, 48, 50, 44, + 50, 0, +} const start int = 1 -const firstFinal int = 44 +const firstFinal int = 172 -const enFail int = 46 +const enScimOnly int = 44 +const enRfc8141Only int = 83 +const enFail int = 193 const enMain int = 1 // Machine is the interface representing the FSM type Machine interface { Error() error Parse(input []byte) (*URN, error) + WithParsingMode(ParsingMode) } type machine struct { @@ -30,12 +101,24 @@ type machine struct { cs int p, pe, eof, pb int err error - tolower []int + startParsingAt int + parsingMode ParsingMode + parsingModeSet bool } // NewMachine creates a new FSM able to parse RFC 2141 strings. -func NewMachine() Machine { - m := &machine{} +func NewMachine(options ...Option) Machine { + m := &machine{ + parsingModeSet: false, + } + + for _, o := range options { + o(m) + } + // Set default parsing mode + if !m.parsingModeSet { + m.WithParsingMode(DefaultParsingMode) + } return m } @@ -51,7 +134,7 @@ func (m *machine) text() []byte { return m.data[m.pb:m.p] } -// Parse parses the input byte array as a RFC 2141 string. +// Parse parses the input byte array as a RFC 2141 or RFC7643 string. func (m *machine) Parse(input []byte) (*URN, error) { m.data = input m.p = 0 @@ -59,1619 +142,4881 @@ func (m *machine) Parse(input []byte) (*URN, error) { m.pe = len(input) m.eof = len(input) m.err = nil - m.tolower = []int{} - output := &URN{} - { - m.cs = start + m.cs = m.startParsingAt + output := &URN{ + tolower: []int{}, } { if (m.p) == (m.pe) { goto _testEof } + if m.cs == 0 { + goto _out + } + _resume: switch m.cs { case 1: - goto stCase1 + switch (m.data)[(m.p)] { + case 85: + goto tr1 + case 117: + goto tr1 + } + goto tr0 case 0: - goto stCase0 + goto _out case 2: - goto stCase2 + switch (m.data)[(m.p)] { + case 82: + goto tr2 + case 114: + goto tr2 + } + goto tr0 case 3: - goto stCase3 + switch (m.data)[(m.p)] { + case 78: + goto tr3 + case 110: + goto tr3 + } + goto tr0 case 4: - goto stCase4 + if (m.data)[(m.p)] == 58 { + goto tr4 + } + goto tr0 case 5: - goto stCase5 + switch (m.data)[(m.p)] { + case 85: + goto tr7 + case 117: + goto tr7 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr6 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr6 + } + default: + goto tr6 + } + goto tr5 case 6: - goto stCase6 + switch (m.data)[(m.p)] { + case 45: + goto tr9 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr9 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr9 + } + default: + goto tr9 + } + goto tr8 case 7: - goto stCase7 + switch (m.data)[(m.p)] { + case 45: + goto tr11 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr11 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr11 + } + default: + goto tr11 + } + goto tr8 case 8: - goto stCase8 + switch (m.data)[(m.p)] { + case 45: + goto tr12 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr12 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr12 + } + default: + goto tr12 + } + goto tr8 case 9: - goto stCase9 + switch (m.data)[(m.p)] { + case 45: + goto tr13 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr13 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr13 + } + default: + goto tr13 + } + goto tr8 case 10: - goto stCase10 + switch (m.data)[(m.p)] { + case 45: + goto tr14 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr14 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr14 + } + default: + goto tr14 + } + goto tr8 case 11: - goto stCase11 + switch (m.data)[(m.p)] { + case 45: + goto tr15 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr15 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr15 + } + default: + goto tr15 + } + goto tr8 case 12: - goto stCase12 + switch (m.data)[(m.p)] { + case 45: + goto tr16 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr16 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr16 + } + default: + goto tr16 + } + goto tr8 case 13: - goto stCase13 + switch (m.data)[(m.p)] { + case 45: + goto tr17 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr17 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr17 + } + default: + goto tr17 + } + goto tr8 case 14: - goto stCase14 + switch (m.data)[(m.p)] { + case 45: + goto tr18 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr18 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr18 + } + default: + goto tr18 + } + goto tr8 case 15: - goto stCase15 + switch (m.data)[(m.p)] { + case 45: + goto tr19 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr19 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr19 + } + default: + goto tr19 + } + goto tr8 case 16: - goto stCase16 + switch (m.data)[(m.p)] { + case 45: + goto tr20 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr20 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr20 + } + default: + goto tr20 + } + goto tr8 case 17: - goto stCase17 + switch (m.data)[(m.p)] { + case 45: + goto tr21 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr21 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr21 + } + default: + goto tr21 + } + goto tr8 case 18: - goto stCase18 + switch (m.data)[(m.p)] { + case 45: + goto tr22 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr22 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr22 + } + default: + goto tr22 + } + goto tr8 case 19: - goto stCase19 + switch (m.data)[(m.p)] { + case 45: + goto tr23 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr23 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr23 + } + default: + goto tr23 + } + goto tr8 case 20: - goto stCase20 + switch (m.data)[(m.p)] { + case 45: + goto tr24 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr24 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr24 + } + default: + goto tr24 + } + goto tr8 case 21: - goto stCase21 + switch (m.data)[(m.p)] { + case 45: + goto tr25 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr25 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr25 + } + default: + goto tr25 + } + goto tr8 case 22: - goto stCase22 + switch (m.data)[(m.p)] { + case 45: + goto tr26 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr26 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr26 + } + default: + goto tr26 + } + goto tr8 case 23: - goto stCase23 + switch (m.data)[(m.p)] { + case 45: + goto tr27 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr27 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr27 + } + default: + goto tr27 + } + goto tr8 case 24: - goto stCase24 + switch (m.data)[(m.p)] { + case 45: + goto tr28 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr28 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr28 + } + default: + goto tr28 + } + goto tr8 case 25: - goto stCase25 + switch (m.data)[(m.p)] { + case 45: + goto tr29 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr29 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr29 + } + default: + goto tr29 + } + goto tr8 case 26: - goto stCase26 + switch (m.data)[(m.p)] { + case 45: + goto tr30 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr30 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr30 + } + default: + goto tr30 + } + goto tr8 case 27: - goto stCase27 + switch (m.data)[(m.p)] { + case 45: + goto tr31 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr31 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr31 + } + default: + goto tr31 + } + goto tr8 case 28: - goto stCase28 + switch (m.data)[(m.p)] { + case 45: + goto tr32 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr32 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr32 + } + default: + goto tr32 + } + goto tr8 case 29: - goto stCase29 + switch (m.data)[(m.p)] { + case 45: + goto tr33 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr33 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr33 + } + default: + goto tr33 + } + goto tr8 case 30: - goto stCase30 + switch (m.data)[(m.p)] { + case 45: + goto tr34 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr34 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr34 + } + default: + goto tr34 + } + goto tr8 case 31: - goto stCase31 + switch (m.data)[(m.p)] { + case 45: + goto tr35 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr35 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr35 + } + default: + goto tr35 + } + goto tr8 case 32: - goto stCase32 + switch (m.data)[(m.p)] { + case 45: + goto tr36 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr36 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr36 + } + default: + goto tr36 + } + goto tr8 case 33: - goto stCase33 + switch (m.data)[(m.p)] { + case 45: + goto tr37 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr37 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr37 + } + default: + goto tr37 + } + goto tr8 case 34: - goto stCase34 + switch (m.data)[(m.p)] { + case 45: + goto tr38 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr38 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr38 + } + default: + goto tr38 + } + goto tr8 case 35: - goto stCase35 + switch (m.data)[(m.p)] { + case 45: + goto tr39 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr39 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr39 + } + default: + goto tr39 + } + goto tr8 case 36: - goto stCase36 + switch (m.data)[(m.p)] { + case 45: + goto tr40 + case 58: + goto tr10 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr40 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr40 + } + default: + goto tr40 + } + goto tr8 case 37: - goto stCase37 + if (m.data)[(m.p)] == 58 { + goto tr10 + } + goto tr8 case 38: - goto stCase38 - case 44: - goto stCase44 + switch (m.data)[(m.p)] { + case 33: + goto tr42 + case 36: + goto tr42 + case 37: + goto tr43 + case 61: + goto tr42 + case 95: + goto tr42 + } + switch { + case (m.data)[(m.p)] < 48: + if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr42 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr42 + } + case (m.data)[(m.p)] >= 64: + goto tr42 + } + default: + goto tr42 + } + goto tr41 + case 172: + switch (m.data)[(m.p)] { + case 33: + goto tr212 + case 36: + goto tr212 + case 37: + goto tr213 + case 61: + goto tr212 + case 95: + goto tr212 + } + switch { + case (m.data)[(m.p)] < 48: + if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr212 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr212 + } + case (m.data)[(m.p)] >= 64: + goto tr212 + } + default: + goto tr212 + } + goto tr41 case 39: - goto stCase39 + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr45 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr45 + } + default: + goto tr46 + } + goto tr44 case 40: - goto stCase40 - case 45: - goto stCase45 + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr47 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr47 + } + default: + goto tr48 + } + goto tr44 + case 173: + switch (m.data)[(m.p)] { + case 33: + goto tr212 + case 36: + goto tr212 + case 37: + goto tr213 + case 61: + goto tr212 + case 95: + goto tr212 + } + switch { + case (m.data)[(m.p)] < 48: + if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr212 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr212 + } + case (m.data)[(m.p)] >= 64: + goto tr212 + } + default: + goto tr212 + } + goto tr44 case 41: - goto stCase41 + switch (m.data)[(m.p)] { + case 45: + goto tr9 + case 58: + goto tr10 + case 82: + goto tr49 + case 114: + goto tr49 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr9 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr9 + } + default: + goto tr9 + } + goto tr5 case 42: - goto stCase42 + switch (m.data)[(m.p)] { + case 45: + goto tr11 + case 58: + goto tr10 + case 78: + goto tr50 + case 110: + goto tr50 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr11 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr11 + } + default: + goto tr11 + } + goto tr5 case 43: - goto stCase43 + if (m.data)[(m.p)] == 45 { + goto tr12 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr12 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr12 + } + default: + goto tr12 + } + goto tr51 + case 44: + switch (m.data)[(m.p)] { + case 85: + goto tr52 + case 117: + goto tr52 + } + goto tr0 + case 45: + switch (m.data)[(m.p)] { + case 82: + goto tr53 + case 114: + goto tr53 + } + goto tr0 case 46: - goto stCase46 - } - goto stOut - stCase1: - switch (m.data)[(m.p)] { + switch (m.data)[(m.p)] { + case 78: + goto tr54 + case 110: + goto tr54 + } + goto tr0 + case 47: + if (m.data)[(m.p)] == 58 { + goto tr55 + } + goto tr0 + case 48: + if (m.data)[(m.p)] == 105 { + goto tr57 + } + goto tr56 + case 49: + if (m.data)[(m.p)] == 101 { + goto tr58 + } + goto tr56 + case 50: + if (m.data)[(m.p)] == 116 { + goto tr59 + } + goto tr56 + case 51: + if (m.data)[(m.p)] == 102 { + goto tr60 + } + goto tr56 + case 52: + if (m.data)[(m.p)] == 58 { + goto tr61 + } + goto tr56 + case 53: + if (m.data)[(m.p)] == 112 { + goto tr62 + } + goto tr56 + case 54: + if (m.data)[(m.p)] == 97 { + goto tr63 + } + goto tr56 + case 55: + if (m.data)[(m.p)] == 114 { + goto tr64 + } + goto tr56 + case 56: + if (m.data)[(m.p)] == 97 { + goto tr65 + } + goto tr56 + case 57: + if (m.data)[(m.p)] == 109 { + goto tr66 + } + goto tr56 + case 58: + if (m.data)[(m.p)] == 115 { + goto tr67 + } + goto tr56 + case 59: + if (m.data)[(m.p)] == 58 { + goto tr68 + } + goto tr56 + case 60: + if (m.data)[(m.p)] == 115 { + goto tr69 + } + goto tr56 + case 61: + if (m.data)[(m.p)] == 99 { + goto tr70 + } + goto tr56 + case 62: + if (m.data)[(m.p)] == 105 { + goto tr71 + } + goto tr56 + case 63: + if (m.data)[(m.p)] == 109 { + goto tr72 + } + goto tr56 + case 64: + if (m.data)[(m.p)] == 58 { + goto tr73 + } + goto tr56 + case 65: + switch (m.data)[(m.p)] { + case 97: + goto tr75 + case 112: + goto tr76 + case 115: + goto tr77 + } + goto tr74 + case 66: + if (m.data)[(m.p)] == 112 { + goto tr78 + } + goto tr74 + case 67: + if (m.data)[(m.p)] == 105 { + goto tr79 + } + goto tr74 + case 68: + if (m.data)[(m.p)] == 58 { + goto tr80 + } + goto tr74 + case 69: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr82 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr82 + } + default: + goto tr82 + } + goto tr81 + case 174: + if (m.data)[(m.p)] == 58 { + goto tr215 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr214 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr214 + } + default: + goto tr214 + } + goto tr81 + case 70: + switch (m.data)[(m.p)] { + case 33: + goto tr84 + case 36: + goto tr84 + case 37: + goto tr85 + case 61: + goto tr84 + case 95: + goto tr84 + } + switch { + case (m.data)[(m.p)] < 48: + if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr84 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr84 + } + case (m.data)[(m.p)] >= 64: + goto tr84 + } + default: + goto tr84 + } + goto tr83 + case 175: + switch (m.data)[(m.p)] { + case 33: + goto tr216 + case 36: + goto tr216 + case 37: + goto tr217 + case 61: + goto tr216 + case 95: + goto tr216 + } + switch { + case (m.data)[(m.p)] < 48: + if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr216 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr216 + } + case (m.data)[(m.p)] >= 64: + goto tr216 + } + default: + goto tr216 + } + goto tr83 + case 71: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr87 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr87 + } + default: + goto tr88 + } + goto tr86 + case 72: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr89 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr89 + } + default: + goto tr90 + } + goto tr86 + case 176: + switch (m.data)[(m.p)] { + case 33: + goto tr216 + case 36: + goto tr216 + case 37: + goto tr217 + case 61: + goto tr216 + case 95: + goto tr216 + } + switch { + case (m.data)[(m.p)] < 48: + if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr216 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr216 + } + case (m.data)[(m.p)] >= 64: + goto tr216 + } + default: + goto tr216 + } + goto tr86 + case 73: + if (m.data)[(m.p)] == 97 { + goto tr91 + } + goto tr74 + case 74: + if (m.data)[(m.p)] == 114 { + goto tr92 + } + goto tr74 + case 75: + if (m.data)[(m.p)] == 97 { + goto tr93 + } + goto tr74 + case 76: + if (m.data)[(m.p)] == 109 { + goto tr79 + } + goto tr74 + case 77: + if (m.data)[(m.p)] == 99 { + goto tr94 + } + goto tr74 + case 78: + if (m.data)[(m.p)] == 104 { + goto tr95 + } + goto tr74 + case 79: + if (m.data)[(m.p)] == 101 { + goto tr96 + } + goto tr74 + case 80: + if (m.data)[(m.p)] == 109 { + goto tr97 + } + goto tr74 + case 81: + if (m.data)[(m.p)] == 97 { + goto tr98 + } + goto tr74 + case 82: + if (m.data)[(m.p)] == 115 { + goto tr79 + } + goto tr74 + case 83: + switch (m.data)[(m.p)] { + case 85: + goto tr99 + case 117: + goto tr99 + } + goto tr0 + case 84: + switch (m.data)[(m.p)] { + case 82: + goto tr100 + case 114: + goto tr100 + } + goto tr0 case 85: - goto tr1 + switch (m.data)[(m.p)] { + case 78: + goto tr101 + case 110: + goto tr101 + } + goto tr0 + case 86: + if (m.data)[(m.p)] == 58 { + goto tr102 + } + goto tr0 + case 87: + switch (m.data)[(m.p)] { + case 85: + goto tr105 + case 117: + goto tr105 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr104 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr104 + } + default: + goto tr104 + } + goto tr103 + case 88: + if (m.data)[(m.p)] == 45 { + goto tr107 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr108 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr108 + } + default: + goto tr108 + } + goto tr106 + case 89: + if (m.data)[(m.p)] == 45 { + goto tr109 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr110 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr110 + } + default: + goto tr110 + } + goto tr106 + case 90: + if (m.data)[(m.p)] == 45 { + goto tr111 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr112 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr112 + } + default: + goto tr112 + } + goto tr106 + case 91: + if (m.data)[(m.p)] == 45 { + goto tr113 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr114 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr114 + } + default: + goto tr114 + } + goto tr106 + case 92: + if (m.data)[(m.p)] == 45 { + goto tr115 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr116 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr116 + } + default: + goto tr116 + } + goto tr106 + case 93: + if (m.data)[(m.p)] == 45 { + goto tr117 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr118 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr118 + } + default: + goto tr118 + } + goto tr106 + case 94: + if (m.data)[(m.p)] == 45 { + goto tr119 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr120 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr120 + } + default: + goto tr120 + } + goto tr106 + case 95: + if (m.data)[(m.p)] == 45 { + goto tr121 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr122 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr122 + } + default: + goto tr122 + } + goto tr106 + case 96: + if (m.data)[(m.p)] == 45 { + goto tr123 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr124 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr124 + } + default: + goto tr124 + } + goto tr106 + case 97: + if (m.data)[(m.p)] == 45 { + goto tr125 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr126 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr126 + } + default: + goto tr126 + } + goto tr106 + case 98: + if (m.data)[(m.p)] == 45 { + goto tr127 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr128 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr128 + } + default: + goto tr128 + } + goto tr106 + case 99: + if (m.data)[(m.p)] == 45 { + goto tr129 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr130 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr130 + } + default: + goto tr130 + } + goto tr106 + case 100: + if (m.data)[(m.p)] == 45 { + goto tr131 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr132 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr132 + } + default: + goto tr132 + } + goto tr106 + case 101: + if (m.data)[(m.p)] == 45 { + goto tr133 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr134 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr134 + } + default: + goto tr134 + } + goto tr106 + case 102: + if (m.data)[(m.p)] == 45 { + goto tr135 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr136 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr136 + } + default: + goto tr136 + } + goto tr106 + case 103: + if (m.data)[(m.p)] == 45 { + goto tr137 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr138 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr138 + } + default: + goto tr138 + } + goto tr106 + case 104: + if (m.data)[(m.p)] == 45 { + goto tr139 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr140 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr140 + } + default: + goto tr140 + } + goto tr106 + case 105: + if (m.data)[(m.p)] == 45 { + goto tr141 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr142 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr142 + } + default: + goto tr142 + } + goto tr106 + case 106: + if (m.data)[(m.p)] == 45 { + goto tr143 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr144 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr144 + } + default: + goto tr144 + } + goto tr106 + case 107: + if (m.data)[(m.p)] == 45 { + goto tr145 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr146 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr146 + } + default: + goto tr146 + } + goto tr106 + case 108: + if (m.data)[(m.p)] == 45 { + goto tr147 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr148 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr148 + } + default: + goto tr148 + } + goto tr106 + case 109: + if (m.data)[(m.p)] == 45 { + goto tr149 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr150 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr150 + } + default: + goto tr150 + } + goto tr106 + case 110: + if (m.data)[(m.p)] == 45 { + goto tr151 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr152 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr152 + } + default: + goto tr152 + } + goto tr106 + case 111: + if (m.data)[(m.p)] == 45 { + goto tr153 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr154 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr154 + } + default: + goto tr154 + } + goto tr106 + case 112: + if (m.data)[(m.p)] == 45 { + goto tr155 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr156 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr156 + } + default: + goto tr156 + } + goto tr106 + case 113: + if (m.data)[(m.p)] == 45 { + goto tr157 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr158 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr158 + } + default: + goto tr158 + } + goto tr106 + case 114: + if (m.data)[(m.p)] == 45 { + goto tr159 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr160 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr160 + } + default: + goto tr160 + } + goto tr106 + case 115: + if (m.data)[(m.p)] == 45 { + goto tr161 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr162 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr162 + } + default: + goto tr162 + } + goto tr106 + case 116: + if (m.data)[(m.p)] == 45 { + goto tr163 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr164 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr164 + } + default: + goto tr164 + } + goto tr106 case 117: - goto tr1 + if (m.data)[(m.p)] == 45 { + goto tr165 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr166 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr166 + } + default: + goto tr166 + } + goto tr106 + case 118: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr167 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr167 + } + default: + goto tr167 + } + goto tr106 + case 119: + if (m.data)[(m.p)] == 58 { + goto tr168 + } + goto tr106 + case 120: + switch (m.data)[(m.p)] { + case 33: + goto tr170 + case 37: + goto tr171 + case 61: + goto tr170 + case 95: + goto tr170 + case 126: + goto tr170 + } + switch { + case (m.data)[(m.p)] < 48: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr170 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr170 + } + case (m.data)[(m.p)] >= 64: + goto tr170 + } + default: + goto tr170 + } + goto tr169 + case 177: + switch (m.data)[(m.p)] { + case 33: + goto tr218 + case 35: + goto tr219 + case 37: + goto tr220 + case 61: + goto tr218 + case 63: + goto tr221 + case 95: + goto tr218 + case 126: + goto tr218 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr218 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr218 + } + default: + goto tr218 + } + goto tr169 + case 178: + switch (m.data)[(m.p)] { + case 33: + goto tr222 + case 37: + goto tr223 + case 61: + goto tr222 + case 95: + goto tr222 + case 126: + goto tr222 + } + switch { + case (m.data)[(m.p)] < 63: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr222 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr222 + } + default: + goto tr222 + } + goto tr183 + case 179: + switch (m.data)[(m.p)] { + case 33: + goto tr224 + case 37: + goto tr225 + case 61: + goto tr224 + case 95: + goto tr224 + case 126: + goto tr224 + } + switch { + case (m.data)[(m.p)] < 63: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr224 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr224 + } + default: + goto tr224 + } + goto tr183 + case 121: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr173 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr173 + } + default: + goto tr174 + } + goto tr172 + case 122: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr175 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr175 + } + default: + goto tr176 + } + goto tr172 + case 180: + switch (m.data)[(m.p)] { + case 33: + goto tr224 + case 37: + goto tr225 + case 61: + goto tr224 + case 95: + goto tr224 + case 126: + goto tr224 + } + switch { + case (m.data)[(m.p)] < 63: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr224 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr224 + } + default: + goto tr224 + } + goto tr172 + case 123: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr178 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr178 + } + default: + goto tr179 + } + goto tr177 + case 124: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr180 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr180 + } + default: + goto tr181 + } + goto tr177 + case 181: + switch (m.data)[(m.p)] { + case 33: + goto tr218 + case 35: + goto tr219 + case 37: + goto tr220 + case 61: + goto tr218 + case 63: + goto tr221 + case 95: + goto tr218 + case 126: + goto tr218 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr218 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr218 + } + default: + goto tr218 + } + goto tr177 + case 125: + switch (m.data)[(m.p)] { + case 43: + goto tr182 + case 61: + goto tr184 + } + goto tr183 + case 126: + switch (m.data)[(m.p)] { + case 33: + goto tr186 + case 37: + goto tr187 + case 61: + goto tr186 + case 63: + goto tr188 + case 95: + goto tr186 + case 126: + goto tr186 + } + switch { + case (m.data)[(m.p)] < 48: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr186 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr186 + } + case (m.data)[(m.p)] >= 64: + goto tr186 + } + default: + goto tr186 + } + goto tr185 + case 182: + switch (m.data)[(m.p)] { + case 33: + goto tr226 + case 35: + goto tr227 + case 37: + goto tr228 + case 61: + goto tr226 + case 63: + goto tr229 + case 95: + goto tr226 + case 126: + goto tr226 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr226 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr226 + } + default: + goto tr226 + } + goto tr185 + case 127: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr190 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr190 + } + default: + goto tr191 + } + goto tr189 + case 128: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr192 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr192 + } + default: + goto tr193 + } + goto tr189 + case 183: + switch (m.data)[(m.p)] { + case 33: + goto tr226 + case 35: + goto tr227 + case 37: + goto tr228 + case 61: + goto tr226 + case 63: + goto tr229 + case 95: + goto tr226 + case 126: + goto tr226 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr226 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr226 + } + default: + goto tr226 + } + goto tr189 + case 184: + switch (m.data)[(m.p)] { + case 33: + goto tr226 + case 35: + goto tr227 + case 37: + goto tr228 + case 43: + goto tr230 + case 61: + goto tr231 + case 63: + goto tr229 + case 95: + goto tr226 + case 126: + goto tr226 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr226 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr226 + } + default: + goto tr226 + } + goto tr185 + case 185: + switch (m.data)[(m.p)] { + case 33: + goto tr232 + case 35: + goto tr233 + case 37: + goto tr234 + case 47: + goto tr226 + case 61: + goto tr232 + case 63: + goto tr235 + case 95: + goto tr232 + case 126: + goto tr232 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr232 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr232 + } + default: + goto tr232 + } + goto tr185 + case 186: + switch (m.data)[(m.p)] { + case 33: + goto tr204 + case 35: + goto tr227 + case 37: + goto tr237 + case 47: + goto tr226 + case 61: + goto tr204 + case 63: + goto tr229 + case 95: + goto tr204 + case 126: + goto tr204 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr204 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr204 + } + default: + goto tr204 + } + goto tr236 + case 187: + switch (m.data)[(m.p)] { + case 33: + goto tr238 + case 35: + goto tr239 + case 37: + goto tr240 + case 61: + goto tr238 + case 63: + goto tr241 + case 95: + goto tr238 + case 126: + goto tr238 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr238 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr238 + } + default: + goto tr238 + } + goto tr203 + case 129: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr195 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr195 + } + default: + goto tr196 + } + goto tr194 + case 130: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr197 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr197 + } + default: + goto tr198 + } + goto tr194 + case 188: + switch (m.data)[(m.p)] { + case 33: + goto tr238 + case 35: + goto tr239 + case 37: + goto tr240 + case 61: + goto tr238 + case 63: + goto tr241 + case 95: + goto tr238 + case 126: + goto tr238 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr238 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr238 + } + default: + goto tr238 + } + goto tr194 + case 189: + switch (m.data)[(m.p)] { + case 33: + goto tr238 + case 35: + goto tr239 + case 37: + goto tr240 + case 61: + goto tr242 + case 63: + goto tr241 + case 95: + goto tr238 + case 126: + goto tr238 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr238 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr238 + } + default: + goto tr238 + } + goto tr203 + case 190: + switch (m.data)[(m.p)] { + case 33: + goto tr243 + case 35: + goto tr244 + case 37: + goto tr245 + case 47: + goto tr238 + case 61: + goto tr243 + case 63: + goto tr246 + case 95: + goto tr243 + case 126: + goto tr243 + } + switch { + case (m.data)[(m.p)] < 64: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 59 { + goto tr243 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr243 + } + default: + goto tr243 + } + goto tr203 + case 131: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr200 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr200 + } + default: + goto tr201 + } + goto tr199 + case 132: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr197 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr197 + } + default: + goto tr198 + } + goto tr199 + case 133: + if (m.data)[(m.p)] == 43 { + goto tr202 + } + goto tr185 + case 191: + switch (m.data)[(m.p)] { + case 33: + goto tr232 + case 35: + goto tr233 + case 37: + goto tr234 + case 61: + goto tr232 + case 63: + goto tr247 + case 95: + goto tr232 + case 126: + goto tr232 + } + switch { + case (m.data)[(m.p)] < 48: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr232 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr232 + } + case (m.data)[(m.p)] >= 64: + goto tr232 + } + default: + goto tr232 + } + goto tr185 + case 134: + switch (m.data)[(m.p)] { + case 43: + goto tr202 + case 61: + goto tr184 + } + goto tr185 + case 135: + switch (m.data)[(m.p)] { + case 33: + goto tr204 + case 37: + goto tr205 + case 61: + goto tr204 + case 63: + goto tr206 + case 95: + goto tr204 + case 126: + goto tr204 + } + switch { + case (m.data)[(m.p)] < 48: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr204 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr204 + } + case (m.data)[(m.p)] >= 64: + goto tr204 + } + default: + goto tr204 + } + goto tr203 + case 136: + if (m.data)[(m.p)] == 61 { + goto tr207 + } + goto tr203 + case 192: + switch (m.data)[(m.p)] { + case 33: + goto tr243 + case 35: + goto tr244 + case 37: + goto tr245 + case 61: + goto tr243 + case 63: + goto tr248 + case 95: + goto tr243 + case 126: + goto tr243 + } + switch { + case (m.data)[(m.p)] < 48: + if 36 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr243 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr243 + } + case (m.data)[(m.p)] >= 64: + goto tr243 + } + default: + goto tr243 + } + goto tr203 + case 137: + if (m.data)[(m.p)] == 58 { + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr167 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr167 + } + default: + goto tr167 + } + goto tr106 + case 138: + switch (m.data)[(m.p)] { + case 45: + goto tr165 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr166 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr166 + } + default: + goto tr166 + } + goto tr106 + case 139: + switch (m.data)[(m.p)] { + case 45: + goto tr163 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr164 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr164 + } + default: + goto tr164 + } + goto tr106 + case 140: + switch (m.data)[(m.p)] { + case 45: + goto tr161 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr162 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr162 + } + default: + goto tr162 + } + goto tr106 + case 141: + switch (m.data)[(m.p)] { + case 45: + goto tr159 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr160 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr160 + } + default: + goto tr160 + } + goto tr106 + case 142: + switch (m.data)[(m.p)] { + case 45: + goto tr157 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr158 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr158 + } + default: + goto tr158 + } + goto tr106 + case 143: + switch (m.data)[(m.p)] { + case 45: + goto tr155 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr156 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr156 + } + default: + goto tr156 + } + goto tr106 + case 144: + switch (m.data)[(m.p)] { + case 45: + goto tr153 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr154 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr154 + } + default: + goto tr154 + } + goto tr106 + case 145: + switch (m.data)[(m.p)] { + case 45: + goto tr151 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr152 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr152 + } + default: + goto tr152 + } + goto tr106 + case 146: + switch (m.data)[(m.p)] { + case 45: + goto tr149 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr150 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr150 + } + default: + goto tr150 + } + goto tr106 + case 147: + switch (m.data)[(m.p)] { + case 45: + goto tr147 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr148 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr148 + } + default: + goto tr148 + } + goto tr106 + case 148: + switch (m.data)[(m.p)] { + case 45: + goto tr145 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr146 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr146 + } + default: + goto tr146 + } + goto tr106 + case 149: + switch (m.data)[(m.p)] { + case 45: + goto tr143 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr144 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr144 + } + default: + goto tr144 + } + goto tr106 + case 150: + switch (m.data)[(m.p)] { + case 45: + goto tr141 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr142 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr142 + } + default: + goto tr142 + } + goto tr106 + case 151: + switch (m.data)[(m.p)] { + case 45: + goto tr139 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr140 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr140 + } + default: + goto tr140 + } + goto tr106 + case 152: + switch (m.data)[(m.p)] { + case 45: + goto tr137 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr138 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr138 + } + default: + goto tr138 + } + goto tr106 + case 153: + switch (m.data)[(m.p)] { + case 45: + goto tr135 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr136 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr136 + } + default: + goto tr136 + } + goto tr106 + case 154: + switch (m.data)[(m.p)] { + case 45: + goto tr133 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr134 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr134 + } + default: + goto tr134 + } + goto tr106 + case 155: + switch (m.data)[(m.p)] { + case 45: + goto tr131 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr132 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr132 + } + default: + goto tr132 + } + goto tr106 + case 156: + switch (m.data)[(m.p)] { + case 45: + goto tr129 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr130 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr130 + } + default: + goto tr130 + } + goto tr106 + case 157: + switch (m.data)[(m.p)] { + case 45: + goto tr127 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr128 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr128 + } + default: + goto tr128 + } + goto tr106 + case 158: + switch (m.data)[(m.p)] { + case 45: + goto tr125 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr126 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr126 + } + default: + goto tr126 + } + goto tr106 + case 159: + switch (m.data)[(m.p)] { + case 45: + goto tr123 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr124 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr124 + } + default: + goto tr124 + } + goto tr106 + case 160: + switch (m.data)[(m.p)] { + case 45: + goto tr121 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr122 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr122 + } + default: + goto tr122 + } + goto tr106 + case 161: + switch (m.data)[(m.p)] { + case 45: + goto tr119 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr120 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr120 + } + default: + goto tr120 + } + goto tr106 + case 162: + switch (m.data)[(m.p)] { + case 45: + goto tr117 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr118 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr118 + } + default: + goto tr118 + } + goto tr106 + case 163: + switch (m.data)[(m.p)] { + case 45: + goto tr115 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr116 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr116 + } + default: + goto tr116 + } + goto tr106 + case 164: + switch (m.data)[(m.p)] { + case 45: + goto tr113 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr114 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr114 + } + default: + goto tr114 + } + goto tr106 + case 165: + switch (m.data)[(m.p)] { + case 45: + goto tr111 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr112 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr112 + } + default: + goto tr112 + } + goto tr106 + case 166: + switch (m.data)[(m.p)] { + case 45: + goto tr109 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr110 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr110 + } + default: + goto tr110 + } + goto tr106 + case 167: + switch (m.data)[(m.p)] { + case 45: + goto tr107 + case 82: + goto tr208 + case 114: + goto tr208 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr108 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr108 + } + default: + goto tr108 + } + goto tr103 + case 168: + switch (m.data)[(m.p)] { + case 45: + goto tr109 + case 58: + goto tr168 + case 78: + goto tr209 + case 110: + goto tr209 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr110 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr110 + } + default: + goto tr110 + } + goto tr103 + case 169: + switch (m.data)[(m.p)] { + case 45: + goto tr210 + case 58: + goto tr168 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr112 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr112 + } + default: + goto tr112 + } + goto tr106 + case 170: + switch (m.data)[(m.p)] { + case 45: + goto tr113 + case 48: + goto tr211 + } + switch { + case (m.data)[(m.p)] < 65: + if 49 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr114 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr211 + } + default: + goto tr211 + } + goto tr106 + case 171: + if (m.data)[(m.p)] == 45 { + goto tr115 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr116 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr116 + } + default: + goto tr116 + } + goto tr106 + case 193: + switch (m.data)[(m.p)] { + case 10: + goto tr183 + case 13: + goto tr183 + } + goto tr249 } - goto tr0 + + tr183: + m.cs = 0 + goto _again tr0: - - m.err = fmt.Errorf(errParse, m.p) - (m.p)-- - - { - goto st46 - } - - goto st0 - tr3: - - m.err = fmt.Errorf(errPrefix, m.p) - (m.p)-- - - { - goto st46 - } - - m.err = fmt.Errorf(errParse, m.p) - (m.p)-- - - { - goto st46 - } - - goto st0 - tr6: - - m.err = fmt.Errorf(errIdentifier, m.p) - (m.p)-- - - { - goto st46 - } - - m.err = fmt.Errorf(errParse, m.p) - (m.p)-- - - { - goto st46 - } - - goto st0 + m.cs = 0 + goto f0 + tr5: + m.cs = 0 + goto f3 + tr8: + m.cs = 0 + goto f5 tr41: - - m.err = fmt.Errorf(errSpecificString, m.p) - (m.p)-- - - { - goto st46 - } - - m.err = fmt.Errorf(errParse, m.p) - (m.p)-- - - { - goto st46 - } - - goto st0 + m.cs = 0 + goto f7 tr44: - - m.err = fmt.Errorf(errHex, m.p) - (m.p)-- - - { - goto st46 - } - - m.err = fmt.Errorf(errSpecificString, m.p) - (m.p)-- - - { - goto st46 - } - - m.err = fmt.Errorf(errParse, m.p) - (m.p)-- - - { - goto st46 - } - - goto st0 + m.cs = 0 + goto f8 + tr51: + m.cs = 0 + goto f10 + tr56: + m.cs = 0 + goto f11 + tr74: + m.cs = 0 + goto f13 + tr81: + m.cs = 0 + goto f15 + tr83: + m.cs = 0 + goto f17 + tr86: + m.cs = 0 + goto f19 + tr103: + m.cs = 0 + goto f20 + tr106: + m.cs = 0 + goto f21 + tr169: + m.cs = 0 + goto f22 + tr172: + m.cs = 0 + goto f23 + tr177: + m.cs = 0 + goto f24 + tr185: + m.cs = 0 + goto f25 + tr189: + m.cs = 0 + goto f27 + tr194: + m.cs = 0 + goto f28 + tr199: + m.cs = 0 + goto f29 + tr203: + m.cs = 0 + goto f30 + tr236: + m.cs = 0 + goto f46 + tr1: + m.cs = 2 + goto f1 + tr2: + m.cs = 3 + goto _again + tr3: + m.cs = 4 + goto _again + tr4: + m.cs = 5 + goto f2 + tr6: + m.cs = 6 + goto f4 + tr9: + m.cs = 7 + goto _again + tr11: + m.cs = 8 + goto _again + tr12: + m.cs = 9 + goto _again + tr13: + m.cs = 10 + goto _again + tr14: + m.cs = 11 + goto _again + tr15: + m.cs = 12 + goto _again + tr16: + m.cs = 13 + goto _again + tr17: + m.cs = 14 + goto _again + tr18: + m.cs = 15 + goto _again + tr19: + m.cs = 16 + goto _again + tr20: + m.cs = 17 + goto _again + tr21: + m.cs = 18 + goto _again + tr22: + m.cs = 19 + goto _again + tr23: + m.cs = 20 + goto _again + tr24: + m.cs = 21 + goto _again + tr25: + m.cs = 22 + goto _again + tr26: + m.cs = 23 + goto _again + tr27: + m.cs = 24 + goto _again + tr28: + m.cs = 25 + goto _again + tr29: + m.cs = 26 + goto _again + tr30: + m.cs = 27 + goto _again + tr31: + m.cs = 28 + goto _again + tr32: + m.cs = 29 + goto _again + tr33: + m.cs = 30 + goto _again + tr34: + m.cs = 31 + goto _again + tr35: + m.cs = 32 + goto _again + tr36: + m.cs = 33 + goto _again + tr37: + m.cs = 34 + goto _again + tr38: + m.cs = 35 + goto _again + tr39: + m.cs = 36 + goto _again + tr40: + m.cs = 37 + goto _again + tr10: + m.cs = 38 + goto f6 + tr213: + m.cs = 39 + goto _again + tr43: + m.cs = 39 + goto f4 + tr45: + m.cs = 40 + goto _again + tr46: + m.cs = 40 + goto f9 + tr7: + m.cs = 41 + goto f1 + tr49: + m.cs = 42 + goto _again tr50: + m.cs = 43 + goto _again + tr52: + m.cs = 45 + goto f1 + tr53: + m.cs = 46 + goto _again + tr54: + m.cs = 47 + goto _again + tr55: + m.cs = 48 + goto f2 + tr57: + m.cs = 49 + goto f4 + tr58: + m.cs = 50 + goto _again + tr59: + m.cs = 51 + goto _again + tr60: + m.cs = 52 + goto _again + tr61: + m.cs = 53 + goto _again + tr62: + m.cs = 54 + goto _again + tr63: + m.cs = 55 + goto _again + tr64: + m.cs = 56 + goto _again + tr65: + m.cs = 57 + goto _again + tr66: + m.cs = 58 + goto _again + tr67: + m.cs = 59 + goto _again + tr68: + m.cs = 60 + goto _again + tr69: + m.cs = 61 + goto _again + tr70: + m.cs = 62 + goto _again + tr71: + m.cs = 63 + goto _again + tr72: + m.cs = 64 + goto _again + tr73: + m.cs = 65 + goto f12 + tr75: + m.cs = 66 + goto f4 + tr78: + m.cs = 67 + goto _again + tr79: + m.cs = 68 + goto _again + tr80: + m.cs = 69 + goto f14 + tr215: + m.cs = 70 + goto f35 + tr217: + m.cs = 71 + goto _again + tr85: + m.cs = 71 + goto f18 + tr87: + m.cs = 72 + goto _again + tr88: + m.cs = 72 + goto f9 + tr76: + m.cs = 73 + goto f4 + tr91: + m.cs = 74 + goto _again + tr92: + m.cs = 75 + goto _again + tr93: + m.cs = 76 + goto _again + tr77: + m.cs = 77 + goto f4 + tr94: + m.cs = 78 + goto _again + tr95: + m.cs = 79 + goto _again + tr96: + m.cs = 80 + goto _again + tr97: + m.cs = 81 + goto _again + tr98: + m.cs = 82 + goto _again + tr99: + m.cs = 84 + goto f1 + tr100: + m.cs = 85 + goto _again + tr101: + m.cs = 86 + goto _again + tr102: + m.cs = 87 + goto f2 + tr104: + m.cs = 88 + goto f4 + tr107: + m.cs = 89 + goto _again + tr109: + m.cs = 90 + goto _again + tr111: + m.cs = 91 + goto _again + tr113: + m.cs = 92 + goto _again + tr115: + m.cs = 93 + goto _again + tr117: + m.cs = 94 + goto _again + tr119: + m.cs = 95 + goto _again + tr121: + m.cs = 96 + goto _again + tr123: + m.cs = 97 + goto _again + tr125: + m.cs = 98 + goto _again + tr127: + m.cs = 99 + goto _again + tr129: + m.cs = 100 + goto _again + tr131: + m.cs = 101 + goto _again + tr133: + m.cs = 102 + goto _again + tr135: + m.cs = 103 + goto _again + tr137: + m.cs = 104 + goto _again + tr139: + m.cs = 105 + goto _again + tr141: + m.cs = 106 + goto _again + tr143: + m.cs = 107 + goto _again + tr145: + m.cs = 108 + goto _again + tr147: + m.cs = 109 + goto _again + tr149: + m.cs = 110 + goto _again + tr151: + m.cs = 111 + goto _again + tr153: + m.cs = 112 + goto _again + tr155: + m.cs = 113 + goto _again + tr157: + m.cs = 114 + goto _again + tr159: + m.cs = 115 + goto _again + tr161: + m.cs = 116 + goto _again + tr163: + m.cs = 117 + goto _again + tr165: + m.cs = 118 + goto _again + tr167: + m.cs = 119 + goto _again + tr168: + m.cs = 120 + goto f6 + tr225: + m.cs = 121 + goto _again + tr223: + m.cs = 121 + goto f4 + tr173: + m.cs = 122 + goto _again + tr174: + m.cs = 122 + goto f9 + tr220: + m.cs = 123 + goto _again + tr171: + m.cs = 123 + goto f4 + tr178: + m.cs = 124 + goto _again + tr179: + m.cs = 124 + goto f9 + tr221: + m.cs = 125 + goto f38 + tr182: + m.cs = 126 + goto _again + tr228: + m.cs = 127 + goto _again + tr187: + m.cs = 127 + goto f26 + tr234: + m.cs = 127 + goto f44 + tr190: + m.cs = 128 + goto _again + tr191: + m.cs = 128 + goto f9 + tr240: + m.cs = 129 + goto _again + tr205: + m.cs = 129 + goto f31 + tr245: + m.cs = 129 + goto f50 + tr195: + m.cs = 130 + goto _again + tr196: + m.cs = 130 + goto f9 + tr237: + m.cs = 131 + goto f31 + tr200: + m.cs = 132 + goto _again + tr201: + m.cs = 132 + goto f9 + tr188: + m.cs = 133 + goto f26 + tr247: + m.cs = 134 + goto f45 + tr184: + m.cs = 135 + goto _again + tr206: + m.cs = 136 + goto f31 + tr248: + m.cs = 136 + goto f50 + tr166: + m.cs = 137 + goto _again + tr164: + m.cs = 138 + goto _again + tr162: + m.cs = 139 + goto _again + tr160: + m.cs = 140 + goto _again + tr158: + m.cs = 141 + goto _again + tr156: + m.cs = 142 + goto _again + tr154: + m.cs = 143 + goto _again + tr152: + m.cs = 144 + goto _again + tr150: + m.cs = 145 + goto _again + tr148: + m.cs = 146 + goto _again + tr146: + m.cs = 147 + goto _again + tr144: + m.cs = 148 + goto _again + tr142: + m.cs = 149 + goto _again + tr140: + m.cs = 150 + goto _again + tr138: + m.cs = 151 + goto _again + tr136: + m.cs = 152 + goto _again + tr134: + m.cs = 153 + goto _again + tr132: + m.cs = 154 + goto _again + tr130: + m.cs = 155 + goto _again + tr128: + m.cs = 156 + goto _again + tr126: + m.cs = 157 + goto _again + tr124: + m.cs = 158 + goto _again + tr122: + m.cs = 159 + goto _again + tr120: + m.cs = 160 + goto _again + tr118: + m.cs = 161 + goto _again + tr116: + m.cs = 162 + goto _again + tr114: + m.cs = 163 + goto _again + tr112: + m.cs = 164 + goto _again + tr110: + m.cs = 165 + goto _again + tr108: + m.cs = 166 + goto _again + tr105: + m.cs = 167 + goto f1 + tr208: + m.cs = 168 + goto _again + tr209: + m.cs = 169 + goto _again + tr210: + m.cs = 170 + goto f2 + tr211: + m.cs = 171 + goto _again + tr212: + m.cs = 172 + goto _again + tr42: + m.cs = 172 + goto f4 + tr47: + m.cs = 173 + goto _again + tr48: + m.cs = 173 + goto f9 + tr214: + m.cs = 174 + goto _again + tr82: + m.cs = 174 + goto f16 + tr216: + m.cs = 175 + goto _again + tr84: + m.cs = 175 + goto f18 + tr89: + m.cs = 176 + goto _again + tr90: + m.cs = 176 + goto f9 + tr218: + m.cs = 177 + goto _again + tr170: + m.cs = 177 + goto f4 + tr219: + m.cs = 178 + goto f38 + tr227: + m.cs = 178 + goto f42 + tr233: + m.cs = 178 + goto f45 + tr239: + m.cs = 178 + goto f48 + tr244: + m.cs = 178 + goto f51 + tr224: + m.cs = 179 + goto _again + tr222: + m.cs = 179 + goto f4 + tr175: + m.cs = 180 + goto _again + tr176: + m.cs = 180 + goto f9 + tr180: + m.cs = 181 + goto _again + tr181: + m.cs = 181 + goto f9 + tr226: + m.cs = 182 + goto _again + tr186: + m.cs = 182 + goto f26 + tr232: + m.cs = 182 + goto f44 + tr192: + m.cs = 183 + goto _again + tr193: + m.cs = 183 + goto f9 + tr229: + m.cs = 184 + goto f42 + tr235: + m.cs = 184 + goto f45 + tr230: + m.cs = 185 + goto _again + tr231: + m.cs = 186 + goto _again + tr238: + m.cs = 187 + goto _again + tr204: + m.cs = 187 + goto f31 + tr243: + m.cs = 187 + goto f50 + tr197: + m.cs = 188 + goto _again + tr198: + m.cs = 188 + goto f9 + tr241: + m.cs = 189 + goto _again + tr246: + m.cs = 189 + goto f50 + tr242: + m.cs = 190 + goto _again + tr202: + m.cs = 191 + goto _again + tr207: + m.cs = 192 + goto _again + tr249: + m.cs = 193 + goto _again + + f4: + + m.pb = m.p + + goto _again + f9: + + // List of positions in the buffer to later lowercase + output.tolower = append(output.tolower, m.p-m.pb) + + goto _again + f2: + + output.prefix = string(m.text()) + + goto _again + f6: + + output.ID = string(m.text()) + + goto _again + f38: + + output.SS = string(m.text()) + // Iterate upper letters lowering them + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] + 32 + } + output.norm = string(m.text()) + // Revert the buffer to the original + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] - 32 + } + + goto _again + f0: m.err = fmt.Errorf(errPrefix, m.p) (m.p)-- - { - goto st46 - } + m.cs = 193 + goto _again + + goto _again + f5: m.err = fmt.Errorf(errIdentifier, m.p) (m.p)-- - { - goto st46 - } + m.cs = 193 + goto _again - m.err = fmt.Errorf(errParse, m.p) + goto _again + f7: + + m.err = fmt.Errorf(errSpecificString, m.p) (m.p)-- - { - goto st46 + m.cs = 193 + goto _again + + goto _again + f23: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + } - goto st0 - tr52: + goto _again + f11: + + m.err = fmt.Errorf(errSCIMNamespace, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f13: + + m.err = fmt.Errorf(errSCIMType, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f15: + + m.err = fmt.Errorf(errSCIMName, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f17: + + if m.p == m.pe { + m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1) + } else { + m.err = fmt.Errorf(errSCIMOther, m.p) + } + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f14: + + output.scim.Type = scimschema.TypeFromString(string(m.text())) + + goto _again + f16: + + output.scim.pos = m.p + + goto _again + f35: + + output.scim.Name = string(m.data[output.scim.pos:m.p]) + + goto _again + f18: + + output.scim.pos = m.p + + goto _again + f22: + + m.err = fmt.Errorf(err8141SpecificString, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f21: + + m.err = fmt.Errorf(err8141Identifier, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f42: + + output.rComponent = string(m.text()) + + goto _again + f48: + + output.qComponent = string(m.text()) + + goto _again + f44: + + if output.rStart { + m.err = fmt.Errorf(err8141RComponentStart, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + output.rStart = true + + goto _again + f50: + + if output.qStart { + m.err = fmt.Errorf(err8141QComponentStart, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + output.qStart = true + + goto _again + f25: + + m.err = fmt.Errorf(err8141MalformedRComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f30: + + m.err = fmt.Errorf(err8141MalformedQComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f1: + + m.pb = m.p + + if m.parsingMode != RFC8141Only { + // Throw an error when: + // - we are entering here matching the the prefix in the namespace identifier part + // - looking ahead (3 chars) we find a colon + if pos := m.p + 3; pos < m.pe && m.data[pos] == 58 && output.prefix != "" { + m.err = fmt.Errorf(errNoUrnWithinID, pos) + (m.p)-- + + m.cs = 193 + goto _again + + } + } + + goto _again + f12: + + output.ID = string(m.text()) + + output.scim = &SCIM{} + + goto _again + f3: + + m.err = fmt.Errorf(errIdentifier, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + m.err = fmt.Errorf(errPrefix, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f10: + + m.err = fmt.Errorf(errIdentifier, m.p) + (m.p)-- + + m.cs = 193 + goto _again m.err = fmt.Errorf(errNoUrnWithinID, m.p) (m.p)-- - { - goto st46 + m.cs = 193 + goto _again + + goto _again + f8: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + } - m.err = fmt.Errorf(errIdentifier, m.p) + m.err = fmt.Errorf(errSpecificString, m.p) (m.p)-- - { - goto st46 + m.cs = 193 + goto _again + + goto _again + f19: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + } - m.err = fmt.Errorf(errParse, m.p) + if m.p == m.pe { + m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1) + } else { + m.err = fmt.Errorf(errSCIMOther, m.p) + } (m.p)-- - { - goto st46 + m.cs = 193 + goto _again + + goto _again + f24: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + } - goto st0 - stCase0: - st0: - m.cs = 0 - goto _out - tr1: + m.err = fmt.Errorf(err8141SpecificString, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f27: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + + m.err = fmt.Errorf(err8141MalformedRComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f28: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + + m.err = fmt.Errorf(err8141MalformedQComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f20: + + m.err = fmt.Errorf(err8141Identifier, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + m.err = fmt.Errorf(errPrefix, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f26: + + if output.rStart { + m.err = fmt.Errorf(err8141RComponentStart, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + output.rStart = true m.pb = m.p - goto st2 - st2: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof2 - } - stCase2: - switch (m.data)[(m.p)] { - case 82: - goto st3 - case 114: - goto st3 - } - goto tr0 - st3: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof3 - } - stCase3: - switch (m.data)[(m.p)] { - case 78: - goto st4 - case 110: - goto st4 - } - goto tr3 - st4: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof4 - } - stCase4: - if (m.data)[(m.p)] == 58 { - goto tr5 - } - goto tr0 - tr5: + goto _again + f45: - output.prefix = string(m.text()) + if output.rStart { + m.err = fmt.Errorf(err8141RComponentStart, m.p) + (m.p)-- + + m.cs = 193 + goto _again - goto st5 - st5: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof5 } - stCase5: - switch (m.data)[(m.p)] { - case 85: - goto tr8 - case 117: - goto tr8 + output.rStart = true + + output.rComponent = string(m.text()) + + goto _again + f31: + + if output.qStart { + m.err = fmt.Errorf(err8141QComponentStart, m.p) + (m.p)-- + + m.cs = 193 + goto _again + } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto tr7 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto tr7 - } - default: - goto tr7 - } - goto tr6 - tr7: + output.qStart = true m.pb = m.p - goto st6 - st6: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof6 - } - stCase6: - switch (m.data)[(m.p)] { - case 45: - goto st7 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st7 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st7 - } - default: - goto st7 - } - goto tr6 - st7: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof7 - } - stCase7: - switch (m.data)[(m.p)] { - case 45: - goto st8 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st8 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st8 - } - default: - goto st8 - } - goto tr6 - st8: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof8 - } - stCase8: - switch (m.data)[(m.p)] { - case 45: - goto st9 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st9 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st9 - } - default: - goto st9 - } - goto tr6 - st9: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof9 - } - stCase9: - switch (m.data)[(m.p)] { - case 45: - goto st10 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st10 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st10 - } - default: - goto st10 - } - goto tr6 - st10: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof10 - } - stCase10: - switch (m.data)[(m.p)] { - case 45: - goto st11 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st11 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st11 - } - default: - goto st11 - } - goto tr6 - st11: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof11 - } - stCase11: - switch (m.data)[(m.p)] { - case 45: - goto st12 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st12 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st12 - } - default: - goto st12 - } - goto tr6 - st12: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof12 - } - stCase12: - switch (m.data)[(m.p)] { - case 45: - goto st13 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st13 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st13 - } - default: - goto st13 - } - goto tr6 - st13: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof13 - } - stCase13: - switch (m.data)[(m.p)] { - case 45: - goto st14 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st14 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st14 - } - default: - goto st14 - } - goto tr6 - st14: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof14 - } - stCase14: - switch (m.data)[(m.p)] { - case 45: - goto st15 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st15 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st15 - } - default: - goto st15 - } - goto tr6 - st15: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof15 - } - stCase15: - switch (m.data)[(m.p)] { - case 45: - goto st16 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st16 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st16 - } - default: - goto st16 - } - goto tr6 - st16: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof16 - } - stCase16: - switch (m.data)[(m.p)] { - case 45: - goto st17 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st17 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st17 - } - default: - goto st17 - } - goto tr6 - st17: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof17 - } - stCase17: - switch (m.data)[(m.p)] { - case 45: - goto st18 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st18 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st18 - } - default: - goto st18 - } - goto tr6 - st18: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof18 - } - stCase18: - switch (m.data)[(m.p)] { - case 45: - goto st19 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st19 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st19 - } - default: - goto st19 - } - goto tr6 - st19: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof19 - } - stCase19: - switch (m.data)[(m.p)] { - case 45: - goto st20 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st20 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st20 - } - default: - goto st20 - } - goto tr6 - st20: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof20 - } - stCase20: - switch (m.data)[(m.p)] { - case 45: - goto st21 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st21 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st21 - } - default: - goto st21 - } - goto tr6 - st21: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof21 - } - stCase21: - switch (m.data)[(m.p)] { - case 45: - goto st22 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st22 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st22 - } - default: - goto st22 - } - goto tr6 - st22: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof22 - } - stCase22: - switch (m.data)[(m.p)] { - case 45: - goto st23 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st23 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st23 - } - default: - goto st23 - } - goto tr6 - st23: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof23 - } - stCase23: - switch (m.data)[(m.p)] { - case 45: - goto st24 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st24 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st24 - } - default: - goto st24 - } - goto tr6 - st24: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof24 - } - stCase24: - switch (m.data)[(m.p)] { - case 45: - goto st25 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st25 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st25 - } - default: - goto st25 - } - goto tr6 - st25: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof25 - } - stCase25: - switch (m.data)[(m.p)] { - case 45: - goto st26 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st26 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st26 - } - default: - goto st26 - } - goto tr6 - st26: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof26 - } - stCase26: - switch (m.data)[(m.p)] { - case 45: - goto st27 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st27 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st27 - } - default: - goto st27 - } - goto tr6 - st27: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof27 - } - stCase27: - switch (m.data)[(m.p)] { - case 45: - goto st28 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st28 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st28 - } - default: - goto st28 - } - goto tr6 - st28: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof28 - } - stCase28: - switch (m.data)[(m.p)] { - case 45: - goto st29 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st29 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st29 - } - default: - goto st29 - } - goto tr6 - st29: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof29 - } - stCase29: - switch (m.data)[(m.p)] { - case 45: - goto st30 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st30 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st30 - } - default: - goto st30 - } - goto tr6 - st30: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof30 - } - stCase30: - switch (m.data)[(m.p)] { - case 45: - goto st31 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st31 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st31 - } - default: - goto st31 - } - goto tr6 - st31: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof31 - } - stCase31: - switch (m.data)[(m.p)] { - case 45: - goto st32 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st32 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st32 - } - default: - goto st32 - } - goto tr6 - st32: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof32 - } - stCase32: - switch (m.data)[(m.p)] { - case 45: - goto st33 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st33 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st33 - } - default: - goto st33 - } - goto tr6 - st33: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof33 - } - stCase33: - switch (m.data)[(m.p)] { - case 45: - goto st34 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st34 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st34 - } - default: - goto st34 - } - goto tr6 - st34: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof34 - } - stCase34: - switch (m.data)[(m.p)] { - case 45: - goto st35 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st35 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st35 - } - default: - goto st35 - } - goto tr6 - st35: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof35 - } - stCase35: - switch (m.data)[(m.p)] { - case 45: - goto st36 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st36 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st36 - } - default: - goto st36 - } - goto tr6 - st36: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof36 - } - stCase36: - switch (m.data)[(m.p)] { - case 45: - goto st37 - case 58: - goto tr10 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st37 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st37 - } - default: - goto st37 - } - goto tr6 - st37: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof37 - } - stCase37: - if (m.data)[(m.p)] == 58 { - goto tr10 - } - goto tr6 - tr10: + goto _again + f51: - output.ID = string(m.text()) + if output.qStart { + m.err = fmt.Errorf(err8141QComponentStart, m.p) + (m.p)-- + + m.cs = 193 + goto _again - goto st38 - st38: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof38 } - stCase38: - switch (m.data)[(m.p)] { + output.qStart = true + + output.qComponent = string(m.text()) + + goto _again + f46: + + m.err = fmt.Errorf(err8141MalformedRComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + m.err = fmt.Errorf(err8141MalformedQComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + f29: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + + m.err = fmt.Errorf(err8141MalformedRComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + m.err = fmt.Errorf(err8141MalformedQComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + goto _again + + _again: + switch _toStateActions[m.cs] { case 33: - goto tr42 - case 36: - goto tr42 - case 37: - goto tr43 - case 61: - goto tr42 - case 95: - goto tr42 - } - switch { - case (m.data)[(m.p)] < 48: - if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { - goto tr42 - } - case (m.data)[(m.p)] > 59: - switch { - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto tr42 - } - case (m.data)[(m.p)] >= 64: - goto tr42 - } - default: - goto tr42 - } - goto tr41 - tr42: - m.pb = m.p + (m.p)-- - goto st44 - st44: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof44 + m.err = fmt.Errorf(err8141InformalID, m.p) + m.cs = 193 + goto _again } - stCase44: - switch (m.data)[(m.p)] { - case 33: - goto st44 - case 36: - goto st44 - case 37: - goto st39 - case 61: - goto st44 - case 95: - goto st44 - } - switch { - case (m.data)[(m.p)] < 48: - if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { - goto st44 - } - case (m.data)[(m.p)] > 59: - switch { - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st44 - } - case (m.data)[(m.p)] >= 64: - goto st44 - } - default: - goto st44 - } - goto tr41 - tr43: - m.pb = m.p - - goto st39 - st39: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof39 + if m.cs == 0 { + goto _out } - stCase39: - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st40 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st40 - } - default: - goto tr46 + if (m.p)++; (m.p) != (m.pe) { + goto _resume } - goto tr44 - tr46: - - m.tolower = append(m.tolower, m.p-m.pb) - - goto st40 - st40: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof40 - } - stCase40: - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st45 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st45 - } - default: - goto tr48 - } - goto tr44 - tr48: - - m.tolower = append(m.tolower, m.p-m.pb) - - goto st45 - st45: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof45 - } - stCase45: - switch (m.data)[(m.p)] { - case 33: - goto st44 - case 36: - goto st44 - case 37: - goto st39 - case 61: - goto st44 - case 95: - goto st44 - } - switch { - case (m.data)[(m.p)] < 48: - if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { - goto st44 - } - case (m.data)[(m.p)] > 59: - switch { - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st44 - } - case (m.data)[(m.p)] >= 64: - goto st44 - } - default: - goto st44 - } - goto tr44 - tr8: - - m.pb = m.p - - goto st41 - st41: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof41 - } - stCase41: - switch (m.data)[(m.p)] { - case 45: - goto st7 - case 58: - goto tr10 - case 82: - goto st42 - case 114: - goto st42 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st7 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st7 - } - default: - goto st7 - } - goto tr6 - st42: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof42 - } - stCase42: - switch (m.data)[(m.p)] { - case 45: - goto st8 - case 58: - goto tr10 - case 78: - goto st43 - case 110: - goto st43 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st8 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st8 - } - default: - goto st8 - } - goto tr50 - st43: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof43 - } - stCase43: - if (m.data)[(m.p)] == 45 { - goto st9 - } - switch { - case (m.data)[(m.p)] < 65: - if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st9 - } - case (m.data)[(m.p)] > 90: - if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st9 - } - default: - goto st9 - } - goto tr52 - st46: - if (m.p)++; (m.p) == (m.pe) { - goto _testEof46 - } - stCase46: - switch (m.data)[(m.p)] { - case 10: - goto st0 - case 13: - goto st0 - } - goto st46 - stOut: - _testEof2: - m.cs = 2 - goto _testEof - _testEof3: - m.cs = 3 - goto _testEof - _testEof4: - m.cs = 4 - goto _testEof - _testEof5: - m.cs = 5 - goto _testEof - _testEof6: - m.cs = 6 - goto _testEof - _testEof7: - m.cs = 7 - goto _testEof - _testEof8: - m.cs = 8 - goto _testEof - _testEof9: - m.cs = 9 - goto _testEof - _testEof10: - m.cs = 10 - goto _testEof - _testEof11: - m.cs = 11 - goto _testEof - _testEof12: - m.cs = 12 - goto _testEof - _testEof13: - m.cs = 13 - goto _testEof - _testEof14: - m.cs = 14 - goto _testEof - _testEof15: - m.cs = 15 - goto _testEof - _testEof16: - m.cs = 16 - goto _testEof - _testEof17: - m.cs = 17 - goto _testEof - _testEof18: - m.cs = 18 - goto _testEof - _testEof19: - m.cs = 19 - goto _testEof - _testEof20: - m.cs = 20 - goto _testEof - _testEof21: - m.cs = 21 - goto _testEof - _testEof22: - m.cs = 22 - goto _testEof - _testEof23: - m.cs = 23 - goto _testEof - _testEof24: - m.cs = 24 - goto _testEof - _testEof25: - m.cs = 25 - goto _testEof - _testEof26: - m.cs = 26 - goto _testEof - _testEof27: - m.cs = 27 - goto _testEof - _testEof28: - m.cs = 28 - goto _testEof - _testEof29: - m.cs = 29 - goto _testEof - _testEof30: - m.cs = 30 - goto _testEof - _testEof31: - m.cs = 31 - goto _testEof - _testEof32: - m.cs = 32 - goto _testEof - _testEof33: - m.cs = 33 - goto _testEof - _testEof34: - m.cs = 34 - goto _testEof - _testEof35: - m.cs = 35 - goto _testEof - _testEof36: - m.cs = 36 - goto _testEof - _testEof37: - m.cs = 37 - goto _testEof - _testEof38: - m.cs = 38 - goto _testEof - _testEof44: - m.cs = 44 - goto _testEof - _testEof39: - m.cs = 39 - goto _testEof - _testEof40: - m.cs = 40 - goto _testEof - _testEof45: - m.cs = 45 - goto _testEof - _testEof41: - m.cs = 41 - goto _testEof - _testEof42: - m.cs = 42 - goto _testEof - _testEof43: - m.cs = 43 - goto _testEof - _testEof46: - m.cs = 46 - goto _testEof - _testEof: { } if (m.p) == (m.eof) { - switch m.cs { - case 44, 45: - - raw := m.text() - output.SS = string(raw) - // Iterate upper letters lowering them - for _, i := range m.tolower { - raw[i] = raw[i] + 32 - } - output.norm = string(raw) - - case 1, 2, 4: - - m.err = fmt.Errorf(errParse, m.p) - (m.p)-- - - { - goto st46 - } - - case 3: + switch _eofActions[m.cs] { + case 1: m.err = fmt.Errorf(errPrefix, m.p) (m.p)-- - { - goto st46 - } + m.cs = 193 + goto _again - m.err = fmt.Errorf(errParse, m.p) - (m.p)-- - - { - goto st46 - } - - case 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 41: + case 6: m.err = fmt.Errorf(errIdentifier, m.p) (m.p)-- - { - goto st46 - } + m.cs = 193 + goto _again - m.err = fmt.Errorf(errParse, m.p) - (m.p)-- - - { - goto st46 - } - - case 38: + case 8: m.err = fmt.Errorf(errSpecificString, m.p) (m.p)-- - { - goto st46 + m.cs = 193 + goto _again + + case 24: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + } - m.err = fmt.Errorf(errParse, m.p) + case 12: + + m.err = fmt.Errorf(errSCIMNamespace, m.p) (m.p)-- - { - goto st46 - } + m.cs = 193 + goto _again - case 42: + case 14: - m.err = fmt.Errorf(errPrefix, m.p) + m.err = fmt.Errorf(errSCIMType, m.p) (m.p)-- - { - goto st46 + m.cs = 193 + goto _again + + case 16: + + m.err = fmt.Errorf(errSCIMName, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + case 18: + + if m.p == m.pe { + m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1) + } else { + m.err = fmt.Errorf(errSCIMOther, m.p) } + (m.p)-- + + m.cs = 193 + goto _again + + case 23: + + m.err = fmt.Errorf(err8141SpecificString, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + case 22: + + m.err = fmt.Errorf(err8141Identifier, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + case 26: + + m.err = fmt.Errorf(err8141MalformedRComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + case 31: + + m.err = fmt.Errorf(err8141MalformedQComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + case 34: + + output.SS = string(m.text()) + // Iterate upper letters lowering them + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] + 32 + } + output.norm = string(m.text()) + // Revert the buffer to the original + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] - 32 + } + + output.kind = RFC2141 + + case 38: + + output.SS = string(m.text()) + // Iterate upper letters lowering them + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] + 32 + } + output.norm = string(m.text()) + // Revert the buffer to the original + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] - 32 + } + + output.kind = RFC8141 + + case 4: m.err = fmt.Errorf(errIdentifier, m.p) (m.p)-- - { - goto st46 - } + m.cs = 193 + goto _again - m.err = fmt.Errorf(errParse, m.p) + m.err = fmt.Errorf(errPrefix, m.p) (m.p)-- - { - goto st46 - } + m.cs = 193 + goto _again - case 43: + case 11: + + m.err = fmt.Errorf(errIdentifier, m.p) + (m.p)-- + + m.cs = 193 + goto _again m.err = fmt.Errorf(errNoUrnWithinID, m.p) (m.p)-- - { - goto st46 - } + m.cs = 193 + goto _again - m.err = fmt.Errorf(errIdentifier, m.p) - (m.p)-- + case 9: - { - goto st46 - } + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- - m.err = fmt.Errorf(errParse, m.p) - (m.p)-- + m.cs = 193 + goto _again - { - goto st46 - } - - case 39, 40: - - m.err = fmt.Errorf(errHex, m.p) - (m.p)-- - - { - goto st46 } m.err = fmt.Errorf(errSpecificString, m.p) (m.p)-- - { - goto st46 + m.cs = 193 + goto _again + + case 20: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + } - m.err = fmt.Errorf(errParse, m.p) + if m.p == m.pe { + m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1) + } else { + m.err = fmt.Errorf(errSCIMOther, m.p) + } (m.p)-- - { - goto st46 + m.cs = 193 + goto _again + + case 25: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + } + + m.err = fmt.Errorf(err8141SpecificString, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + case 28: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + + m.err = fmt.Errorf(err8141MalformedRComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + case 29: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + + m.err = fmt.Errorf(err8141MalformedQComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + case 21: + + m.err = fmt.Errorf(err8141Identifier, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + m.err = fmt.Errorf(errPrefix, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + case 42: + + output.rComponent = string(m.text()) + + output.kind = RFC8141 + + case 48: + + output.qComponent = string(m.text()) + + output.kind = RFC8141 + + case 41: + + output.fComponent = string(m.text()) + + output.kind = RFC8141 + + case 40: + + m.pb = m.p + + output.fComponent = string(m.text()) + + output.kind = RFC8141 + + case 30: + + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + + m.err = fmt.Errorf(err8141MalformedRComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + m.err = fmt.Errorf(err8141MalformedQComp, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + case 35: + + output.scim.Name = string(m.data[output.scim.pos:m.p]) + + output.SS = string(m.text()) + // Iterate upper letters lowering them + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] + 32 + } + output.norm = string(m.text()) + // Revert the buffer to the original + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] - 32 + } + + output.kind = RFC7643 + + case 37: + + output.scim.Other = string(m.data[output.scim.pos:m.p]) + + output.SS = string(m.text()) + // Iterate upper letters lowering them + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] + 32 + } + output.norm = string(m.text()) + // Revert the buffer to the original + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] - 32 + } + + output.kind = RFC7643 + + case 44: + + if output.rStart { + m.err = fmt.Errorf(err8141RComponentStart, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + output.rStart = true + + output.rComponent = string(m.text()) + + output.kind = RFC8141 + + case 50: + + if output.qStart { + m.err = fmt.Errorf(err8141QComponentStart, m.p) + (m.p)-- + + m.cs = 193 + goto _again + + } + output.qStart = true + + output.qComponent = string(m.text()) + + output.kind = RFC8141 } } @@ -1686,3 +5031,16 @@ func (m *machine) Parse(input []byte) (*URN, error) { return output, nil } + +func (m *machine) WithParsingMode(x ParsingMode) { + m.parsingMode = x + switch m.parsingMode { + case RFC2141Only: + m.startParsingAt = enMain + case RFC8141Only: + m.startParsingAt = enRfc8141Only + case RFC7643Only: + m.startParsingAt = enScimOnly + } + m.parsingModeSet = true +} diff --git a/vendor/github.com/leodido/go-urn/machine.go.rl b/vendor/github.com/leodido/go-urn/machine.go.rl index 3bc05a651..0a1742199 100644 --- a/vendor/github.com/leodido/go-urn/machine.go.rl +++ b/vendor/github.com/leodido/go-urn/machine.go.rl @@ -2,15 +2,28 @@ package urn import ( "fmt" + + scimschema "github.com/leodido/go-urn/scim/schema" ) var ( - errPrefix = "expecting the prefix to be the \"urn\" string (whatever case) [col %d]" - errIdentifier = "expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col %d]" - errSpecificString = "expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col %d]" - errNoUrnWithinID = "expecting the identifier to not contain the \"urn\" reserved string [col %d]" - errHex = "expecting the specific string hex chars to be well-formed (%%alnum{2}) [col %d]" - errParse = "parsing error [col %d]" + errPrefix = "expecting the prefix to be the \"urn\" string (whatever case) [col %d]" + errIdentifier = "expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col %d]" + errSpecificString = "expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col %d]" + errNoUrnWithinID = "expecting the identifier to not contain the \"urn\" reserved string [col %d]" + errHex = "expecting the percent encoded chars to be well-formed (%%alnum{2}) [col %d]" + errSCIMNamespace = "expecing the SCIM namespace identifier (ietf:params:scim) [col %d]" + errSCIMType = "expecting a correct SCIM type (schemas, api, param) [col %d]" + errSCIMName = "expecting one or more alnum char in the SCIM name part [col %d]" + errSCIMOther = "expecting a well-formed other SCIM part [col %d]" + errSCIMOtherIncomplete = "expecting a not empty SCIM other part after colon [col %d]" + err8141InformalID = "informal URN namespace must be in the form urn-[1-9][0-9] [col %d]" + err8141SpecificString = "expecting the specific string to contain alnum, hex, or others ([~&()+,-.:=@;$_!*'] or [/?] not in first position) chars [col %d]" + err8141Identifier = "expecting the indentifier to be a string with (length 2 to 32 chars) containing alnum (or dashes) not starting or ending with a dash [col %d]" + err8141RComponentStart = "expecting only one r-component (starting with the ?+ sequence) [col %d]" + err8141QComponentStart = "expecting only one q-component (starting with the ?= sequence) [col %d]" + err8141MalformedRComp = "expecting a non-empty r-component containing alnum, hex, or others ([~&()+,-.:=@;$_!*'] or [/?] but not at its beginning) [col %d]" + err8141MalformedQComp = "expecting a non-empty q-component containing alnum, hex, or others ([~&()+,-.:=@;$_!*'] or [/?] but not at its beginning) [col %d]" ) %%{ @@ -24,25 +37,42 @@ action mark { } action tolower { - m.tolower = append(m.tolower, m.p - m.pb) + // List of positions in the buffer to later lowercase + output.tolower = append(output.tolower, m.p - m.pb) } action set_pre { output.prefix = string(m.text()) } +action throw_pre_urn_err { + if m.parsingMode != RFC8141Only { + // Throw an error when: + // - we are entering here matching the the prefix in the namespace identifier part + // - looking ahead (3 chars) we find a colon + if pos := m.p + 3; pos < m.pe && m.data[pos] == 58 && output.prefix != "" { + m.err = fmt.Errorf(errNoUrnWithinID, pos) + fhold; + fgoto fail; + } + } +} + action set_nid { output.ID = string(m.text()) } action set_nss { - raw := m.text() - output.SS = string(raw) + output.SS = string(m.text()) // Iterate upper letters lowering them - for _, i := range m.tolower { - raw[i] = raw[i] + 32 + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] + 32 + } + output.norm = string(m.text()) + // Revert the buffer to the original + for _, i := range output.tolower { + m.data[m.pb+i] = m.data[m.pb+i] - 32 } - output.norm = string(raw) } action err_pre { @@ -70,20 +100,20 @@ action err_urn { } action err_hex { - m.err = fmt.Errorf(errHex, m.p) - fhold; - fgoto fail; + if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only { + m.err = fmt.Errorf(errHex, m.p) + fhold; + fgoto fail; + } } -action err_parse { - m.err = fmt.Errorf(errParse, m.p) - fhold; - fgoto fail; +action base_type { + output.kind = RFC2141; } -pre = ([uU][rR][nN] @err(err_pre)) >mark %set_pre; +pre = ([uU] @err(err_pre) [rR] @err(err_pre) [nN] @err(err_pre)) >mark >throw_pre_urn_err %set_pre; -nid = (alnum >mark (alnum | '-'){0,31}) %set_nid; +nid = (alnum >mark (alnum | '-'){0,31}) $err(err_nid) %set_nid; hex = '%' (digit | lower | upper >tolower){2} $err(err_hex); @@ -91,9 +121,179 @@ sss = (alnum | [()+,\-.:=@;$_!*']); nss = (sss | hex)+ $err(err_nss); +nid_not_urn = (nid - pre %err(err_urn)); + +urn = pre ':' @err(err_pre) (nid_not_urn ':' nss >mark %set_nss) %eof(base_type); + +### SCIM BEG + +action err_scim_nid { + m.err = fmt.Errorf(errSCIMNamespace, m.p) + fhold; + fgoto fail; +} + +action err_scim_type { + m.err = fmt.Errorf(errSCIMType, m.p) + fhold; + fgoto fail; +} + +action err_scim_name { + m.err = fmt.Errorf(errSCIMName, m.p) + fhold; + fgoto fail; +} + +action err_scim_other { + if m.p == m.pe { + m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1) + } else { + m.err = fmt.Errorf(errSCIMOther, m.p) + } + fhold; + fgoto fail; +} + +action scim_type { + output.kind = RFC7643; +} + +action create_scim { + output.scim = &SCIM{}; +} + +action set_scim_type { + output.scim.Type = scimschema.TypeFromString(string(m.text())) +} + +action mark_scim_name { + output.scim.pos = m.p +} + +action set_scim_name { + output.scim.Name = string(m.data[output.scim.pos:m.p]) +} + +action mark_scim_other { + output.scim.pos = m.p +} + +action set_scim_other { + output.scim.Other = string(m.data[output.scim.pos:m.p]) +} + +scim_nid = 'ietf:params:scim' >mark %set_nid %create_scim $err(err_scim_nid); + +scim_other = ':' (sss | hex)+ >mark_scim_other %set_scim_other $err(err_scim_other); + +scim_name = (alnum)+ >mark_scim_name %set_scim_name $err(err_scim_name); + +scim_type = ('schemas' | 'api' | 'param') >mark %set_scim_type $err(err_scim_type); + +scim_only := pre ':' @err(err_pre) (scim_nid ':' scim_type ':' scim_name scim_other? %set_nss) %eof(scim_type); + +### SCIM END + +### 8141 BEG + +action err_nss_8141 { + m.err = fmt.Errorf(err8141SpecificString, m.p) + fhold; + fgoto fail; +} + +action err_nid_8141 { + m.err = fmt.Errorf(err8141Identifier, m.p) + fhold; + fgoto fail; +} + +action rfc8141_type { + output.kind = RFC8141; +} + +action set_r_component { + output.rComponent = string(m.text()) +} + +action set_q_component { + output.qComponent = string(m.text()) +} + +action set_f_component { + output.fComponent = string(m.text()) +} + +action informal_nid_match { + fhold; + m.err = fmt.Errorf(err8141InformalID, m.p); + fgoto fail; +} + +action mark_r_start { + if output.rStart { + m.err = fmt.Errorf(err8141RComponentStart, m.p) + fhold; + fgoto fail; + } + output.rStart = true +} + +action mark_q_start { + if output.qStart { + m.err = fmt.Errorf(err8141QComponentStart, m.p) + fhold; + fgoto fail; + } + output.qStart = true +} + +action err_malformed_r_component { + m.err = fmt.Errorf(err8141MalformedRComp, m.p) + fhold; + fgoto fail; +} + +action err_malformed_q_component { + m.err = fmt.Errorf(err8141MalformedQComp, m.p) + fhold; + fgoto fail; +} + +pchar = (sss | '~' | '&' | hex); + +component = pchar (pchar | '/' | '?')*; + +r_start = ('?+') %mark_r_start; + +r_component = r_start <: (r_start | component)+ $err(err_malformed_r_component) >mark %set_r_component; + +q_start = ('?=') %mark_q_start; + +q_component = q_start <: (q_start | component)+ $err(err_malformed_q_component) >mark %set_q_component; + +rq_components = (r_component :>> q_component? | q_component); + +fragment = (pchar | '/' | '?')*; + +f_component = '#' fragment >mark %set_f_component; + +nss_rfc8141 = (pchar >mark (pchar | '/')*) $err(err_nss_8141) %set_nss; + +nid_rfc8141 = (alnum >mark (alnum | '-'){0,30} alnum) $err(err_nid_8141) %set_nid; + +informal_id = pre ('-' [a-zA-z0] %to(informal_nid_match)); + +nid_rfc8141_not_urn = (nid_rfc8141 - informal_id?); + +rfc8141_only := pre ':' @err(err_pre) nid_rfc8141_not_urn ':' nss_rfc8141 rq_components? f_component? %eof(rfc8141_type); + +### 8141 END + fail := (any - [\n\r])* @err{ fgoto main; }; -main := (pre ':' (nid - pre %err(err_urn)) $err(err_nid) ':' nss >mark %set_nss) $err(err_parse); +main := urn; }%% @@ -103,6 +303,7 @@ main := (pre ':' (nid - pre %err(err_urn)) $err(err_nid) ':' nss >mark %set_nss) type Machine interface { Error() error Parse(input []byte) (*URN, error) + WithParsingMode(ParsingMode) } type machine struct { @@ -110,12 +311,24 @@ type machine struct { cs int p, pe, eof, pb int err error - tolower []int + startParsingAt int + parsingMode ParsingMode + parsingModeSet bool } // NewMachine creates a new FSM able to parse RFC 2141 strings. -func NewMachine() Machine { - m := &machine{} +func NewMachine(options ...Option) Machine { + m := &machine{ + parsingModeSet: false, + } + + for _, o := range options { + o(m) + } + // Set default parsing mode + if !m.parsingModeSet { + m.WithParsingMode(DefaultParsingMode) + } %% access m.; %% variable p m.p; @@ -137,7 +350,7 @@ func (m *machine) text() []byte { return m.data[m.pb:m.p] } -// Parse parses the input byte array as a RFC 2141 string. +// Parse parses the input byte array as a RFC 2141 or RFC7643 string. func (m *machine) Parse(input []byte) (*URN, error) { m.data = input m.p = 0 @@ -145,10 +358,11 @@ func (m *machine) Parse(input []byte) (*URN, error) { m.pe = len(input) m.eof = len(input) m.err = nil - m.tolower = []int{} - output := &URN{} + m.cs = m.startParsingAt + output := &URN{ + tolower: []int{}, + } - %% write init; %% write exec; if m.cs < first_final || m.cs == en_fail { @@ -157,3 +371,16 @@ func (m *machine) Parse(input []byte) (*URN, error) { return output, nil } + +func (m *machine) WithParsingMode(x ParsingMode) { + m.parsingMode = x + switch m.parsingMode { + case RFC2141Only: + m.startParsingAt = en_main + case RFC8141Only: + m.startParsingAt = en_rfc8141_only + case RFC7643Only: + m.startParsingAt = en_scim_only + } + m.parsingModeSet = true +} \ No newline at end of file diff --git a/vendor/github.com/leodido/go-urn/makefile b/vendor/github.com/leodido/go-urn/makefile index df87cdc6d..68d5dd0f1 100644 --- a/vendor/github.com/leodido/go-urn/makefile +++ b/vendor/github.com/leodido/go-urn/makefile @@ -15,18 +15,24 @@ clean: .PHONY: images images: docs/urn.png +.PHONY: snake2camel +snake2camel: + @cd ./tools/snake2camel; go build -o ../../snake2camel . + .PHONY: removecomments removecomments: @cd ./tools/removecomments; go build -o ../../removecomments . machine.go: machine.go.rl +machine.go: snake2camel + machine.go: removecomments machine.go: - $(RAGEL) -Z -G2 -e -o $@ $< + $(RAGEL) -Z -G1 -e -o $@ $< @./removecomments $@ - $(MAKE) -s file=$@ snake2camel + @./snake2camel $@ $(GOFMT) $@ docs/urn.dot: machine.go.rl @@ -41,13 +47,5 @@ bench: *_test.go machine.go go test -bench=. -benchmem -benchtime=5s ./... .PHONY: tests -tests: *_test.go +tests: *_test.go $(GO_TEST) ./... - -.PHONY: snake2camel -snake2camel: - @awk -i inplace '{ \ - while ( match($$0, /(.*)([a-z]+[0-9]*)_([a-zA-Z0-9])(.*)/, cap) ) \ - $$0 = cap[1] cap[2] toupper(cap[3]) cap[4]; \ - print \ - }' $(file) diff --git a/vendor/github.com/leodido/go-urn/options.go b/vendor/github.com/leodido/go-urn/options.go new file mode 100644 index 000000000..c543835a2 --- /dev/null +++ b/vendor/github.com/leodido/go-urn/options.go @@ -0,0 +1,9 @@ +package urn + +type Option func(Machine) + +func WithParsingMode(mode ParsingMode) Option { + return func(m Machine) { + m.WithParsingMode(mode) + } +} diff --git a/vendor/github.com/leodido/go-urn/parsing_mode.go b/vendor/github.com/leodido/go-urn/parsing_mode.go new file mode 100644 index 000000000..fce5aadc3 --- /dev/null +++ b/vendor/github.com/leodido/go-urn/parsing_mode.go @@ -0,0 +1,12 @@ +package urn + +type ParsingMode int + +const ( + Default ParsingMode = iota + RFC2141Only + RFC7643Only + RFC8141Only +) + +const DefaultParsingMode = RFC2141Only diff --git a/vendor/github.com/leodido/go-urn/scim.go b/vendor/github.com/leodido/go-urn/scim.go new file mode 100644 index 000000000..f6b7aefba --- /dev/null +++ b/vendor/github.com/leodido/go-urn/scim.go @@ -0,0 +1,48 @@ +package urn + +import ( + "encoding/json" + "fmt" + + scimschema "github.com/leodido/go-urn/scim/schema" +) + +const errInvalidSCIMURN = "invalid SCIM URN: %s" + +type SCIM struct { + Type scimschema.Type + Name string + Other string + pos int +} + +func (s SCIM) MarshalJSON() ([]byte, error) { + return json.Marshal(s.String()) +} + +func (s *SCIM) UnmarshalJSON(bytes []byte) error { + var str string + if err := json.Unmarshal(bytes, &str); err != nil { + return err + } + // Parse as SCIM + value, ok := Parse([]byte(str), WithParsingMode(RFC7643Only)) + if !ok { + return fmt.Errorf(errInvalidSCIMURN, str) + } + if value.RFC() != RFC7643 { + return fmt.Errorf(errInvalidSCIMURN, str) + } + *s = *value.SCIM() + + return nil +} + +func (s *SCIM) String() string { + ret := fmt.Sprintf("urn:ietf:params:scim:%s:%s", s.Type.String(), s.Name) + if s.Other != "" { + ret += fmt.Sprintf(":%s", s.Other) + } + + return ret +} diff --git a/vendor/github.com/leodido/go-urn/scim/schema/type.go b/vendor/github.com/leodido/go-urn/scim/schema/type.go new file mode 100644 index 000000000..134918230 --- /dev/null +++ b/vendor/github.com/leodido/go-urn/scim/schema/type.go @@ -0,0 +1,36 @@ +package scimschema + +type Type int + +const ( + Unsupported Type = iota + Schemas + API + Param +) + +func (t Type) String() string { + switch t { + case Schemas: + return "schemas" + case API: + return "api" + case Param: + return "param" + } + + return "" +} + +func TypeFromString(input string) Type { + switch input { + case "schemas": + return Schemas + case "api": + return API + case "param": + return Param + } + + return Unsupported +} diff --git a/vendor/github.com/leodido/go-urn/urn.go b/vendor/github.com/leodido/go-urn/urn.go index d51a6c915..894d6258d 100644 --- a/vendor/github.com/leodido/go-urn/urn.go +++ b/vendor/github.com/leodido/go-urn/urn.go @@ -16,10 +16,18 @@ const errInvalidURN = "invalid URN: %s" // // Details at https://tools.ietf.org/html/rfc2141. type URN struct { - prefix string // Static prefix. Equal to "urn" when empty. - ID string // Namespace identifier - SS string // Namespace specific string - norm string // Normalized namespace specific string + prefix string // Static prefix. Equal to "urn" when empty. + ID string // Namespace identifier (NID) + SS string // Namespace specific string (NSS) + norm string // Normalized namespace specific string + kind Kind + scim *SCIM + rComponent string // RFC8141 + qComponent string // RFC8141 + fComponent string // RFC8141 + rStart bool // RFC8141 + qStart bool // RFC8141 + tolower []int } // Normalize turns the receiving URN into its norm version. @@ -30,12 +38,21 @@ func (u *URN) Normalize() *URN { prefix: "urn", ID: strings.ToLower(u.ID), SS: u.norm, + // rComponent: u.rComponent, + // qComponent: u.qComponent, + // fComponent: u.fComponent, } } // Equal checks the lexical equivalence of the current URN with another one. func (u *URN) Equal(x *URN) bool { - return *u.Normalize() == *x.Normalize() + if x == nil { + return false + } + nu := u.Normalize() + nx := x.Normalize() + + return nu.prefix == nx.prefix && nu.ID == nx.ID && nu.SS == nx.SS } // String reassembles the URN into a valid URN string. @@ -51,14 +68,23 @@ func (u *URN) String() string { res += "urn" } res += u.prefix + ":" + u.ID + ":" + u.SS + if u.rComponent != "" { + res += "?+" + u.rComponent + } + if u.qComponent != "" { + res += "?=" + u.qComponent + } + if u.fComponent != "" { + res += "#" + u.fComponent + } } return res } -// Parse is responsible to create an URN instance from a byte array matching the correct URN syntax. -func Parse(u []byte) (*URN, bool) { - urn, err := NewMachine().Parse(u) +// Parse is responsible to create an URN instance from a byte array matching the correct URN syntax (RFC 2141). +func Parse(u []byte, options ...Option) (*URN, bool) { + urn, err := NewMachine(options...).Parse(u) if err != nil { return nil, false } @@ -71,7 +97,7 @@ func (u URN) MarshalJSON() ([]byte, error) { return json.Marshal(u.String()) } -// MarshalJSON unmarshals a URN from JSON string form (e.g. `"urn:oid:1.2.3.4"`). +// UnmarshalJSON unmarshals a URN from JSON string form (e.g. `"urn:oid:1.2.3.4"`). func (u *URN) UnmarshalJSON(bytes []byte) error { var str string if err := json.Unmarshal(bytes, &str); err != nil { @@ -82,5 +108,34 @@ func (u *URN) UnmarshalJSON(bytes []byte) error { } else { *u = *value } + return nil -} \ No newline at end of file +} + +func (u *URN) IsSCIM() bool { + return u.kind == RFC7643 +} + +func (u *URN) SCIM() *SCIM { + if u.kind != RFC7643 { + return nil + } + + return u.scim +} + +func (u *URN) RFC() Kind { + return u.kind +} + +func (u *URN) FComponent() string { + return u.fComponent +} + +func (u *URN) QComponent() string { + return u.qComponent +} + +func (u *URN) RComponent() string { + return u.rComponent +} diff --git a/vendor/github.com/leodido/go-urn/urn8141.go b/vendor/github.com/leodido/go-urn/urn8141.go new file mode 100644 index 000000000..da4dd062e --- /dev/null +++ b/vendor/github.com/leodido/go-urn/urn8141.go @@ -0,0 +1,30 @@ +package urn + +import ( + "encoding/json" + "fmt" +) + +const errInvalidURN8141 = "invalid URN per RFC 8141: %s" + +type URN8141 struct { + *URN +} + +func (u URN8141) MarshalJSON() ([]byte, error) { + return json.Marshal(u.String()) +} + +func (u *URN8141) UnmarshalJSON(bytes []byte) error { + var str string + if err := json.Unmarshal(bytes, &str); err != nil { + return err + } + if value, ok := Parse([]byte(str), WithParsingMode(RFC8141Only)); !ok { + return fmt.Errorf(errInvalidURN8141, str) + } else { + *u = URN8141{value} + } + + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 074155c39..6ee1ff361 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -980,7 +980,7 @@ github.com/go-playground/locales/en # github.com/go-playground/universal-translator v0.18.1 ## explicit; go 1.18 github.com/go-playground/universal-translator -# github.com/go-playground/validator/v10 v10.17.0 +# github.com/go-playground/validator/v10 v10.18.0 ## explicit; go 1.18 github.com/go-playground/validator/v10 github.com/go-playground/validator/v10/translations/en @@ -1249,9 +1249,10 @@ github.com/klauspost/compress/s2 # github.com/klauspost/cpuid/v2 v2.1.0 ## explicit; go 1.15 github.com/klauspost/cpuid/v2 -# github.com/leodido/go-urn v1.2.4 -## explicit; go 1.16 +# github.com/leodido/go-urn v1.4.0 +## explicit; go 1.18 github.com/leodido/go-urn +github.com/leodido/go-urn/scim/schema # github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1 ## explicit; go 1.13 github.com/leonelquinteros/gotext From 8b403a4b86606f365114eb8986d1f887537a0db5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 06:13:10 +0000 Subject: [PATCH 084/112] build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp Bumps [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.47.0 to 0.48.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.47.0...zpages/v0.48.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- .../net/http/otelhttp/common.go | 20 ++- .../net/http/otelhttp/handler.go | 12 +- .../otelhttp/internal/semconvutil/httpconv.go | 95 +++++----- .../otelhttp/internal/semconvutil/netconv.go | 163 ------------------ .../net/http/otelhttp/transport.go | 109 +++++++++++- .../net/http/otelhttp/version.go | 2 +- vendor/modules.txt | 2 +- 9 files changed, 169 insertions(+), 240 deletions(-) diff --git a/go.mod b/go.mod index 9888c1928..eed8672f9 100644 --- a/go.mod +++ b/go.mod @@ -88,7 +88,7 @@ require ( github.com/xhit/go-simple-mail/v2 v2.16.0 go-micro.dev/v4 v4.9.0 go.etcd.io/bbolt v1.3.8 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 go.opentelemetry.io/contrib/zpages v0.48.0 go.opentelemetry.io/otel v1.23.1 go.opentelemetry.io/otel/exporters/jaeger v1.17.0 diff --git a/go.sum b/go.sum index 152eab8e8..2e1ec30f1 100644 --- a/go.sum +++ b/go.sum @@ -2094,8 +2094,8 @@ go.opentelemetry.io/contrib v1.0.0 h1:khwDCxdSspjOLmFnvMuSHd/5rPzbTx0+l6aURwtQdf go.opentelemetry.io/contrib v1.0.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= go.opentelemetry.io/contrib/zpages v0.48.0 h1:7vnqarYRu2VYvUUwrLWXRk5HqPuCbtuZdgR4eaVeP3Y= go.opentelemetry.io/contrib/zpages v0.48.0/go.mod h1:fzewgBzh126TFUgnNhI9/1hjvnZes7wBKXAMQmyM01g= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go index 9509014e8..c6f438774 100644 --- a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go @@ -31,10 +31,24 @@ const ( // Server HTTP metrics. const ( - RequestCount = "http.server.request_count" // Incoming request count total - RequestContentLength = "http.server.request_content_length" // Incoming request bytes total + // Deprecated: This field is unused. + RequestCount = "http.server.request_count" // Incoming request count total + // Deprecated: Use of this field has been migrated to serverRequestSize. It will be removed in a future version. + RequestContentLength = "http.server.request_content_length" // Incoming request bytes total + // Deprecated: Use of this field has been migrated to serverResponseSize. It will be removed in a future version. ResponseContentLength = "http.server.response_content_length" // Incoming response bytes total - ServerLatency = "http.server.duration" // Incoming end to end duration, milliseconds + // Deprecated: Use of this field has been migrated to serverDuration. It will be removed in a future version. + ServerLatency = "http.server.duration" // Incoming end to end duration, milliseconds + serverRequestSize = "http.server.request.size" // Incoming request bytes total + serverResponseSize = "http.server.response.size" // Incoming response bytes total + serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds +) + +// Client HTTP metrics. +const ( + clientRequestSize = "http.client.request.size" // Outgoing request bytes total + clientResponseSize = "http.client.response.size" // Outgoing response bytes total + clientDuration = "http.client.duration" // Outgoing end to end duration, milliseconds ) // Filter is a predicate used to determine whether a given http.request should diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go index af84f0e4b..3d292dab6 100644 --- a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go @@ -108,23 +108,23 @@ func handleErr(err error) { func (h *middleware) createMeasures() { var err error h.requestBytesCounter, err = h.meter.Int64Counter( - RequestContentLength, + serverRequestSize, metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request content length (uncompressed)"), + metric.WithDescription("Measures the size of HTTP request messages."), ) handleErr(err) h.responseBytesCounter, err = h.meter.Int64Counter( - ResponseContentLength, + serverResponseSize, metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response content length (uncompressed)"), + metric.WithDescription("Measures the size of HTTP response messages."), ) handleErr(err) h.serverLatencyMeasure, err = h.meter.Float64Histogram( - ServerLatency, + serverDuration, metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of HTTP request handling"), + metric.WithDescription("Measures the duration of inbound HTTP requests."), ) handleErr(err) } diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go index 794d4c26a..495d700cf 100644 --- a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go @@ -51,6 +51,14 @@ func HTTPClientRequest(req *http.Request) []attribute.KeyValue { return hc.ClientRequest(req) } +// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. +// The following attributes are always returned: "http.method", "net.peer.name". +// The following attributes are returned if the +// related values are defined in req: "net.peer.port". +func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { + return hc.ClientRequestMetrics(req) +} + // HTTPClientStatus returns a span status code and message for an HTTP status code // value received by a client. func HTTPClientStatus(code int) (codes.Code, string) { @@ -114,36 +122,6 @@ func HTTPServerStatus(code int) (codes.Code, string) { return hc.ServerStatus(code) } -// HTTPRequestHeader returns the contents of h as attributes. -// -// Instrumentation should require an explicit configuration of which headers to -// captured and then prune what they pass here. Including all headers can be a -// security risk - explicit configuration helps avoid leaking sensitive -// information. -// -// The User-Agent header is already captured in the http.user_agent attribute -// from ClientRequest and ServerRequest. Instrumentation may provide an option -// to capture that header here even though it is not recommended. Otherwise, -// instrumentation should filter that out of what is passed. -func HTTPRequestHeader(h http.Header) []attribute.KeyValue { - return hc.RequestHeader(h) -} - -// HTTPResponseHeader returns the contents of h as attributes. -// -// Instrumentation should require an explicit configuration of which headers to -// captured and then prune what they pass here. Including all headers can be a -// security risk - explicit configuration helps avoid leaking sensitive -// information. -// -// The User-Agent header is already captured in the http.user_agent attribute -// from ClientRequest and ServerRequest. Instrumentation may provide an option -// to capture that header here even though it is not recommended. Otherwise, -// instrumentation should filter that out of what is passed. -func HTTPResponseHeader(h http.Header) []attribute.KeyValue { - return hc.ResponseHeader(h) -} - // httpConv are the HTTP semantic convention attributes defined for a version // of the OpenTelemetry specification. type httpConv struct { @@ -286,6 +264,38 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { return attrs } +// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The +// following attributes are always returned: "http.method", "net.peer.name". +// The following attributes are returned if the related values +// are defined in req: "net.peer.port". +func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { + /* The following semantic conventions are returned if present: + http.method string + net.peer.name string + net.peer.port int + */ + + n := 2 // method, peer name. + var h string + if req.URL != nil { + h = req.URL.Host + } + peer, p := firstHostPort(h, req.Header.Get("Host")) + port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) + if port > 0 { + n++ + } + + attrs := make([]attribute.KeyValue, 0, n) + attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) + + if port > 0 { + attrs = append(attrs, c.NetConv.PeerPort(port)) + } + + return attrs +} + // ServerRequest returns attributes for an HTTP request received by a server. // // The server must be the primary server name if it is known. For example this @@ -551,31 +561,6 @@ func firstHostPort(source ...string) (host string, port int) { return } -// RequestHeader returns the contents of h as OpenTelemetry attributes. -func (c *httpConv) RequestHeader(h http.Header) []attribute.KeyValue { - return c.header("http.request.header", h) -} - -// ResponseHeader returns the contents of h as OpenTelemetry attributes. -func (c *httpConv) ResponseHeader(h http.Header) []attribute.KeyValue { - return c.header("http.response.header", h) -} - -func (c *httpConv) header(prefix string, h http.Header) []attribute.KeyValue { - key := func(k string) attribute.Key { - k = strings.ToLower(k) - k = strings.ReplaceAll(k, "-", "_") - k = fmt.Sprintf("%s.%s", prefix, k) - return attribute.Key(k) - } - - attrs := make([]attribute.KeyValue, 0, len(h)) - for k, v := range h { - attrs = append(attrs, key(k).StringSlice(v)) - } - return attrs -} - // ClientStatus returns a span status code and message for an HTTP status code // value received by a client. func (c *httpConv) ClientStatus(code int) (codes.Code, string) { diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go index cb4cb9355..d3a06e0ca 100644 --- a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go @@ -32,24 +32,6 @@ func NetTransport(network string) attribute.KeyValue { return nc.Transport(network) } -// NetClient returns trace attributes for a client network connection to address. -// See net.Dial for information about acceptable address values, address should -// be the same as the one used to create conn. If conn is nil, only network -// peer attributes will be returned that describe address. Otherwise, the -// socket level information about conn will also be included. -func NetClient(address string, conn net.Conn) []attribute.KeyValue { - return nc.Client(address, conn) -} - -// NetServer returns trace attributes for a network listener listening at address. -// See net.Listen for information about acceptable address values, address -// should be the same as the one used to create ln. If ln is nil, only network -// host attributes will be returned that describe address. Otherwise, the -// socket level information about ln will also be included. -func NetServer(address string, ln net.Listener) []attribute.KeyValue { - return nc.Server(address, ln) -} - // netConv are the network semantic convention attributes defined for a version // of the OpenTelemetry specification. type netConv struct { @@ -125,52 +107,6 @@ func (c *netConv) Host(address string) []attribute.KeyValue { return attrs } -// Server returns attributes for a network listener listening at address. See -// net.Listen for information about acceptable address values, address should -// be the same as the one used to create ln. If ln is nil, only network host -// attributes will be returned that describe address. Otherwise, the socket -// level information about ln will also be included. -func (c *netConv) Server(address string, ln net.Listener) []attribute.KeyValue { - if ln == nil { - return c.Host(address) - } - - lAddr := ln.Addr() - if lAddr == nil { - return c.Host(address) - } - - hostName, hostPort := splitHostPort(address) - sockHostAddr, sockHostPort := splitHostPort(lAddr.String()) - network := lAddr.Network() - sockFamily := family(network, sockHostAddr) - - n := nonZeroStr(hostName, network, sockHostAddr, sockFamily) - n += positiveInt(hostPort, sockHostPort) - attr := make([]attribute.KeyValue, 0, n) - if hostName != "" { - attr = append(attr, c.HostName(hostName)) - if hostPort > 0 { - // Only if net.host.name is set should net.host.port be. - attr = append(attr, c.HostPort(hostPort)) - } - } - if network != "" { - attr = append(attr, c.Transport(network)) - } - if sockFamily != "" { - attr = append(attr, c.NetSockFamilyKey.String(sockFamily)) - } - if sockHostAddr != "" { - attr = append(attr, c.NetSockHostAddrKey.String(sockHostAddr)) - if sockHostPort > 0 { - // Only if net.sock.host.addr is set should net.sock.host.port be. - attr = append(attr, c.NetSockHostPortKey.Int(sockHostPort)) - } - } - return attr -} - func (c *netConv) HostName(name string) attribute.KeyValue { return c.NetHostNameKey.String(name) } @@ -179,85 +115,6 @@ func (c *netConv) HostPort(port int) attribute.KeyValue { return c.NetHostPortKey.Int(port) } -// Client returns attributes for a client network connection to address. See -// net.Dial for information about acceptable address values, address should be -// the same as the one used to create conn. If conn is nil, only network peer -// attributes will be returned that describe address. Otherwise, the socket -// level information about conn will also be included. -func (c *netConv) Client(address string, conn net.Conn) []attribute.KeyValue { - if conn == nil { - return c.Peer(address) - } - - lAddr, rAddr := conn.LocalAddr(), conn.RemoteAddr() - - var network string - switch { - case lAddr != nil: - network = lAddr.Network() - case rAddr != nil: - network = rAddr.Network() - default: - return c.Peer(address) - } - - peerName, peerPort := splitHostPort(address) - var ( - sockFamily string - sockPeerAddr string - sockPeerPort int - sockHostAddr string - sockHostPort int - ) - - if lAddr != nil { - sockHostAddr, sockHostPort = splitHostPort(lAddr.String()) - } - - if rAddr != nil { - sockPeerAddr, sockPeerPort = splitHostPort(rAddr.String()) - } - - switch { - case sockHostAddr != "": - sockFamily = family(network, sockHostAddr) - case sockPeerAddr != "": - sockFamily = family(network, sockPeerAddr) - } - - n := nonZeroStr(peerName, network, sockPeerAddr, sockHostAddr, sockFamily) - n += positiveInt(peerPort, sockPeerPort, sockHostPort) - attr := make([]attribute.KeyValue, 0, n) - if peerName != "" { - attr = append(attr, c.PeerName(peerName)) - if peerPort > 0 { - // Only if net.peer.name is set should net.peer.port be. - attr = append(attr, c.PeerPort(peerPort)) - } - } - if network != "" { - attr = append(attr, c.Transport(network)) - } - if sockFamily != "" { - attr = append(attr, c.NetSockFamilyKey.String(sockFamily)) - } - if sockPeerAddr != "" { - attr = append(attr, c.NetSockPeerAddrKey.String(sockPeerAddr)) - if sockPeerPort > 0 { - // Only if net.sock.peer.addr is set should net.sock.peer.port be. - attr = append(attr, c.NetSockPeerPortKey.Int(sockPeerPort)) - } - } - if sockHostAddr != "" { - attr = append(attr, c.NetSockHostAddrKey.String(sockHostAddr)) - if sockHostPort > 0 { - // Only if net.sock.host.addr is set should net.sock.host.port be. - attr = append(attr, c.NetSockHostPortKey.Int(sockHostPort)) - } - } - return attr -} - func family(network, address string) string { switch network { case "unix", "unixgram", "unixpacket": @@ -273,26 +130,6 @@ func family(network, address string) string { return "" } -func nonZeroStr(strs ...string) int { - var n int - for _, str := range strs { - if str != "" { - n++ - } - } - return n -} - -func positiveInt(ints ...int) int { - var n int - for _, i := range ints { - if i > 0 { - n++ - } - } - return n -} - // Peer returns attributes for a network peer address. func (c *netConv) Peer(address string) []attribute.KeyValue { h, p := splitHostPort(address) diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go index e835cac12..8d850df3b 100644 --- a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go @@ -19,31 +19,43 @@ import ( "io" "net/http" "net/http/httptrace" + "sync/atomic" + "time" + + "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" + + semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // Transport implements the http.RoundTripper interface and wraps -// outbound HTTP(S) requests with a span. +// outbound HTTP(S) requests with a span and enriches it with metrics. type Transport struct { rt http.RoundTripper tracer trace.Tracer + meter metric.Meter propagators propagation.TextMapPropagator spanStartOptions []trace.SpanStartOption filters []Filter spanNameFormatter func(string, *http.Request) string clientTrace func(context.Context) *httptrace.ClientTrace + + requestBytesCounter metric.Int64Counter + responseBytesCounter metric.Int64Counter + latencyMeasure metric.Float64Histogram } var _ http.RoundTripper = &Transport{} // NewTransport wraps the provided http.RoundTripper with one that -// starts a span and injects the span context into the outbound request headers. +// starts a span, injects the span context into the outbound request headers, +// and enriches it with metrics. // // If the provided http.RoundTripper is nil, http.DefaultTransport will be used // as the base http.RoundTripper. @@ -63,12 +75,14 @@ func NewTransport(base http.RoundTripper, opts ...Option) *Transport { c := newConfig(append(defaultOpts, opts...)...) t.applyConfig(c) + t.createMeasures() return &t } func (t *Transport) applyConfig(c *config) { t.tracer = c.Tracer + t.meter = c.Meter t.propagators = c.Propagators t.spanStartOptions = c.SpanStartOptions t.filters = c.Filters @@ -76,6 +90,30 @@ func (t *Transport) applyConfig(c *config) { t.clientTrace = c.ClientTrace } +func (t *Transport) createMeasures() { + var err error + t.requestBytesCounter, err = t.meter.Int64Counter( + clientRequestSize, + metric.WithUnit("By"), + metric.WithDescription("Measures the size of HTTP request messages."), + ) + handleErr(err) + + t.responseBytesCounter, err = t.meter.Int64Counter( + clientResponseSize, + metric.WithUnit("By"), + metric.WithDescription("Measures the size of HTTP response messages."), + ) + handleErr(err) + + t.latencyMeasure, err = t.meter.Float64Histogram( + clientDuration, + metric.WithUnit("ms"), + metric.WithDescription("Measures the duration of outbound HTTP requests."), + ) + handleErr(err) +} + func defaultTransportFormatter(_ string, r *http.Request) string { return "HTTP " + r.Method } @@ -84,6 +122,7 @@ func defaultTransportFormatter(_ string, r *http.Request) string { // before handing the request to the configured base RoundTripper. The created span will // end when the response body is closed or when a read from the body returns io.EOF. func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { + requestStartTime := time.Now() for _, f := range t.filters { if !f(r) { // Simply pass through to the base RoundTripper if a filter rejects the request @@ -109,7 +148,23 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { ctx = httptrace.WithClientTrace(ctx, t.clientTrace(ctx)) } + labeler := &Labeler{} + ctx = injectLabeler(ctx, labeler) + r = r.Clone(ctx) // According to RoundTripper spec, we shouldn't modify the origin request. + + // use a body wrapper to determine the request size + var bw bodyWrapper + // if request body is nil or NoBody, we don't want to mutate the body as it + // will affect the identity of it in an unforeseeable way because we assert + // ReadCloser fulfills a certain interface and it is indeed nil or NoBody. + if r.Body != nil && r.Body != http.NoBody { + bw.ReadCloser = r.Body + // noop to prevent nil panic. not using this record fun yet. + bw.record = func(int64) {} + r.Body = &bw + } + span.SetAttributes(semconvutil.HTTPClientRequest(r)...) t.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header)) @@ -121,9 +176,28 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { return res, err } + // metrics + metricAttrs := append(labeler.Get(), semconvutil.HTTPClientRequestMetrics(r)...) + if res.StatusCode > 0 { + metricAttrs = append(metricAttrs, semconv.HTTPStatusCode(res.StatusCode)) + } + o := metric.WithAttributes(metricAttrs...) + t.requestBytesCounter.Add(ctx, bw.read, o) + // For handling response bytes we leverage a callback when the client reads the http response + readRecordFunc := func(n int64) { + t.responseBytesCounter.Add(ctx, n, o) + } + + // traces span.SetAttributes(semconvutil.HTTPClientResponse(res)...) span.SetStatus(semconvutil.HTTPClientStatus(res.StatusCode)) - res.Body = newWrappedBody(span, res.Body) + + res.Body = newWrappedBody(span, readRecordFunc, res.Body) + + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond) + + t.latencyMeasure.Record(ctx, elapsedTime, o) return res, err } @@ -131,17 +205,17 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { // newWrappedBody returns a new and appropriately scoped *wrappedBody as an // io.ReadCloser. If the passed body implements io.Writer, the returned value // will implement io.ReadWriteCloser. -func newWrappedBody(span trace.Span, body io.ReadCloser) io.ReadCloser { +func newWrappedBody(span trace.Span, record func(n int64), body io.ReadCloser) io.ReadCloser { // The successful protocol switch responses will have a body that // implement an io.ReadWriteCloser. Ensure this interface type continues // to be satisfied if that is the case. if _, ok := body.(io.ReadWriteCloser); ok { - return &wrappedBody{span: span, body: body} + return &wrappedBody{span: span, record: record, body: body} } // Remove the implementation of the io.ReadWriteCloser and only implement // the io.ReadCloser. - return struct{ io.ReadCloser }{&wrappedBody{span: span, body: body}} + return struct{ io.ReadCloser }{&wrappedBody{span: span, record: record, body: body}} } // wrappedBody is the response body type returned by the transport @@ -153,8 +227,11 @@ func newWrappedBody(span trace.Span, body io.ReadCloser) io.ReadCloser { // If the response body implements the io.Writer interface (i.e. for // successful protocol switches), the wrapped body also will. type wrappedBody struct { - span trace.Span - body io.ReadCloser + span trace.Span + recorded atomic.Bool + record func(n int64) + body io.ReadCloser + read atomic.Int64 } var _ io.ReadWriteCloser = &wrappedBody{} @@ -171,11 +248,14 @@ func (wb *wrappedBody) Write(p []byte) (int, error) { func (wb *wrappedBody) Read(b []byte) (int, error) { n, err := wb.body.Read(b) + // Record the number of bytes read + wb.read.Add(int64(n)) switch err { case nil: // nothing to do here but fall through to the return case io.EOF: + wb.recordBytesRead() wb.span.End() default: wb.span.RecordError(err) @@ -184,7 +264,20 @@ func (wb *wrappedBody) Read(b []byte) (int, error) { return n, err } +// recordBytesRead is a function that ensures the number of bytes read is recorded once and only once. +func (wb *wrappedBody) recordBytesRead() { + // note: it is more performant (and equally correct) to use atomic.Bool over sync.Once here. In the event that + // two goroutines are racing to call this method, the number of bytes read will no longer increase. Using + // CompareAndSwap allows later goroutines to return quickly and not block waiting for the race winner to finish + // calling wb.record(wb.read.Load()). + if wb.recorded.CompareAndSwap(false, true) { + // Record the total number of bytes read + wb.record(wb.read.Load()) + } +} + func (wb *wrappedBody) Close() error { + wb.recordBytesRead() wb.span.End() if wb.body != nil { return wb.body.Close() diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go index 9a4a02143..7499f688b 100644 --- a/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go @@ -16,7 +16,7 @@ package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http // Version is the current release version of the otelhttp instrumentation. func Version() string { - return "0.47.0" + return "0.48.0" // This string is updated by the pre_release.sh script during release } diff --git a/vendor/modules.txt b/vendor/modules.txt index 6ee1ff361..0700f1c62 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1935,7 +1935,7 @@ go.opentelemetry.io/contrib ## explicit; go 1.20 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal -# go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 +# go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 ## explicit; go 1.20 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil From 47a3f3b7eedc0588d21dc485f62ab0ea10135745 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 7 Feb 2024 16:24:05 +0100 Subject: [PATCH 085/112] [test] check if the image is really the problem Signed-off-by: Christian Richter --- .drone.star | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.star b/.drone.star index 3f86efd1f..dc8bcb32a 100644 --- a/.drone.star +++ b/.drone.star @@ -1824,6 +1824,7 @@ def docs(): "image": OC_CI_GOLANG, "environment": DRONE_HTTP_PROXY_ENV, "commands": [ + "apk update && apk add asciidoctor" "make -C docs test", ], }, From e636850902b723e44ea29d4fa24b27fab3d619ae Mon Sep 17 00:00:00 2001 From: mmattel Date: Mon, 12 Feb 2024 17:14:56 +0100 Subject: [PATCH 086/112] [docs-only] Fixing changelog references pointing to the wrong PR --- changelog/5.0.0-rc.1_2023-12-27/access-token-issuer.md | 2 +- .../allow-configuring-additional-routes.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog/5.0.0-rc.1_2023-12-27/access-token-issuer.md b/changelog/5.0.0-rc.1_2023-12-27/access-token-issuer.md index 3519938ad..fc31a070b 100644 --- a/changelog/5.0.0-rc.1_2023-12-27/access-token-issuer.md +++ b/changelog/5.0.0-rc.1_2023-12-27/access-token-issuer.md @@ -2,4 +2,4 @@ Enhancement: Support spec violating AD FS access token issuer AD FS `/adfs/.well-known/openid-configuration` has an optional `access_token_issuer` which, in violation of the OpenID Connect spec, takes precedence over `issuer`. -https://github.com/owncloud/ocis/pull/7138 +https://github.com/owncloud/ocis/pull/7140 diff --git a/changelog/5.0.0-rc.1_2023-12-27/allow-configuring-additional-routes.md b/changelog/5.0.0-rc.1_2023-12-27/allow-configuring-additional-routes.md index 449d4fcd1..23b548b4c 100644 --- a/changelog/5.0.0-rc.1_2023-12-27/allow-configuring-additional-routes.md +++ b/changelog/5.0.0-rc.1_2023-12-27/allow-configuring-additional-routes.md @@ -1,5 +1,5 @@ Enhancement: Allow configuring additional routes -Allows adding custom routes to the ocis proxy. This enables customs ocis extensions +Allows adding custom routes to the ocis proxy. This enables custom ocis extensions. -https://github.com/owncloud/ocis/pull/7740 +https://github.com/owncloud/ocis/pull/7741 From fa6f198e01213816f3b80ef1d61fa982ce3bac51 Mon Sep 17 00:00:00 2001 From: mmattel Date: Mon, 12 Feb 2024 18:03:23 +0100 Subject: [PATCH 087/112] [docs-only] Add more envvars to the removed table --- .../env-var-deltas/4.0.0-5.0.0-added.adoc | 2 +- .../env-var-deltas/4.0.0-5.0.0-removed.adoc | 11 ++++++++++- .../env-var-deltas/4.0.0-5.0.0-removed.md | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc index 061e54b1c..5fe2337a9 100644 --- a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc @@ -1,7 +1,7 @@ // # Added Variables between oCIS 4.0.0 and oCIS 5.0.0 // commenting the headline to make it better includable -// table created based on the .md file per 2024.02.06 +// table created based on the .md file per 2024.02.12 // the table should be recreated/updated based on the final .md file [width="100%",cols="~,~,~,~",options="header"] diff --git a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc index 04560ee38..34120a5ae 100644 --- a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.adoc @@ -1,7 +1,7 @@ // Removed Variables between oCIS 4.0.0 and oCIS 5.0.0 // commenting the headline to make it better includable -// table created based on the .md file per 2024.02.07 +// table created based on the .md file per 2024.02.12 // the table should be recreated/updated based on the final .md file [width="100%",cols="~,~,~,~",options="header"] @@ -43,4 +43,13 @@ | `OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` | Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's. +| xref:{s-path}/web.adoc[web] +| `WEB_OPTION_IMPRINT_URL` +| To be configured via the `theme.json` file +| Specifies the target URL for the imprint link valid for the ocis instance in the account menu. + +| +| `WEB_OPTION_PRIVACY_URL` +| To be configured via the `theme.json` file +| Specifies the target URL for the privacy link valid for the ocis instance in the account menu. |=== diff --git a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.md b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.md index a7dcecb71..f20c695c0 100644 --- a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.md +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-removed.md @@ -8,4 +8,6 @@ | services/idp/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | | services/sharing/pkg/config/config.go | `SHARING_EVENTS_TLS_ROOT_CA_CERT` | `SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE` | The root CA certificate used to validate the server's TLS certificate. If provided SHARING_EVENTS_TLS_INSECURE will be seen as false. | | services/users/pkg/config/config.go | `LDAP_BIND_PASSWORD` | `OCIS_LDAP_BIND_PASSWORD` | Password to use for authenticating the 'bind_dn'. | -| services/users/pkg/config/config.go | `LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` | `OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` | Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's. | +| | `LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` | `OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING` | Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user ID's. | +| services/web/pkg/config/options.go | `WEB_OPTION_IMPRINT_URL` | To be configured via the `theme.json` file | Specifies the target URL for the imprint link valid for the ocis instance in the account menu. | +| | `WEB_OPTION_PRIVACY_URL` | To be configured via the `theme.json` file | Specifies the target URL for the privacy link valid for the ocis instance in the account menu. | From 8ef56e15abaeda3e129b45ce447de9aea5e1f131 Mon Sep 17 00:00:00 2001 From: mmattel Date: Mon, 12 Feb 2024 18:45:35 +0100 Subject: [PATCH 088/112] password policies change request --- changelog/5.0.0-rc.1_2023-12-27/passwod-policies-renaming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/5.0.0-rc.1_2023-12-27/passwod-policies-renaming.md b/changelog/5.0.0-rc.1_2023-12-27/passwod-policies-renaming.md index a82ee8af9..62c188844 100644 --- a/changelog/5.0.0-rc.1_2023-12-27/passwod-policies-renaming.md +++ b/changelog/5.0.0-rc.1_2023-12-27/passwod-policies-renaming.md @@ -2,4 +2,4 @@ Enhancement: The password policies change request The variables renaming OCIS-3767 -https://github.com/cs3org/reva/pull/7264 +https://github.com/cs3org/reva/pull/4409 From 6f1d0ee86a9b29fb0b6a474603e2a1805ac2d9d6 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Mon, 12 Feb 2024 23:39:22 +0100 Subject: [PATCH 089/112] docs: fix urls in changelog --- CHANGELOG.md | 24 +++++++++---------- .../passwod-policies-renaming.md | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0b04d93..2f01d58e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -466,12 +466,12 @@ The following sections list the changes for 5.0.0-rc.1. * Enhancement - Add the Banned Passwords List: [#4197](https://github.com/cs3org/reva/pull/4197) * Enhancement - Introduce service accounts: [#6427](https://github.com/owncloud/ocis/pull/6427) * Enhancement - SSE for messaging: [#6992](https://github.com/owncloud/ocis/pull/6992) -* Enhancement - Support spec violating AD FS access token issuer: [#7138](https://github.com/owncloud/ocis/pull/7138) +* Enhancement - Support spec violating AD FS access token issuer: [#7140](https://github.com/owncloud/ocis/pull/7140) * Enhancement - Add OCIS_LDAP_BIND_PASSWORD as replacement for LDAP_BIND_PASSWORD: [#7176](https://github.com/owncloud/ocis/issues/7176) * Enhancement - Keyword Query Language (KQL) search syntax: [#7212](https://github.com/owncloud/ocis/pull/7212) * Enhancement - Introduce clientlog service: [#7217](https://github.com/owncloud/ocis/pull/7217) * Enhancement - Proxy uses service accounts for provisioning: [#7240](https://github.com/owncloud/ocis/pull/7240) -* Enhancement - The password policies change request: [#7264](https://github.com/cs3org/reva/pull/7264) +* Enhancement - The password policies change request: [#7264](https://github.com/owncloud/ocis/pull/7264) * Enhancement - Introduce natsjs registry: [#7272](https://github.com/owncloud/ocis/issues/7272) * Enhancement - Add the password policies: [#7285](https://github.com/owncloud/ocis/pull/7285) * Enhancement - Add login URL config: [#7317](https://github.com/owncloud/ocis/pull/7317) @@ -497,7 +497,7 @@ The following sections list the changes for 5.0.0-rc.1. * Enhancement - Add new permissions: [#7700](https://github.com/owncloud/ocis/pull/7700) * Enhancement - Add preferred language to user settings: [#7720](https://github.com/owncloud/ocis/pull/7720) * Enhancement - Add user filter startswith and contains: [#7739](https://github.com/owncloud/ocis/pull/7739) -* Enhancement - Allow configuring additional routes: [#7740](https://github.com/owncloud/ocis/pull/7740) +* Enhancement - Allow configuring additional routes: [#7741](https://github.com/owncloud/ocis/pull/7741) * Enhancement - Default link permission config: [#7783](https://github.com/owncloud/ocis/pull/7783) * Enhancement - Add banned password list to the default deployments: [#7784](https://github.com/owncloud/ocis/pull/7784) * Enhancement - Update to go 1.21: [#7794](https://github.com/owncloud/ocis/pull/7794) @@ -867,13 +867,13 @@ The following sections list the changes for 5.0.0-rc.1. https://github.com/owncloud/ocis/pull/6992 -* Enhancement - Support spec violating AD FS access token issuer: [#7138](https://github.com/owncloud/ocis/pull/7138) +* Enhancement - Support spec violating AD FS access token issuer: [#7140](https://github.com/owncloud/ocis/pull/7140) AD FS `/adfs/.well-known/openid-configuration` has an optional `access_token_issuer` which, in violation of the OpenID Connect spec, takes precedence over `issuer`. - https://github.com/owncloud/ocis/pull/7138 + https://github.com/owncloud/ocis/pull/7140 * Enhancement - Add OCIS_LDAP_BIND_PASSWORD as replacement for LDAP_BIND_PASSWORD: [#7176](https://github.com/owncloud/ocis/issues/7176) @@ -954,11 +954,11 @@ The following sections list the changes for 5.0.0-rc.1. https://github.com/owncloud/ocis/issues/5550 https://github.com/owncloud/ocis/pull/7240 -* Enhancement - The password policies change request: [#7264](https://github.com/cs3org/reva/pull/7264) +* Enhancement - The password policies change request: [#7264](https://github.com/owncloud/ocis/pull/7264) The variables renaming OCIS-3767 - https://github.com/cs3org/reva/pull/7264 + https://github.com/owncloud/ocis/pull/7264 * Enhancement - Introduce natsjs registry: [#7272](https://github.com/owncloud/ocis/issues/7272) @@ -1262,12 +1262,12 @@ The following sections list the changes for 5.0.0-rc.1. https://github.com/owncloud/ocis/issues/5486 https://github.com/owncloud/ocis/pull/7739 -* Enhancement - Allow configuring additional routes: [#7740](https://github.com/owncloud/ocis/pull/7740) +* Enhancement - Allow configuring additional routes: [#7741](https://github.com/owncloud/ocis/pull/7741) Allows adding custom routes to the ocis proxy. This enables customs ocis extensions - https://github.com/owncloud/ocis/pull/7740 + https://github.com/owncloud/ocis/pull/7741 * Enhancement - Default link permission config: [#7783](https://github.com/owncloud/ocis/pull/7783) @@ -1823,7 +1823,7 @@ The following sections list the changes for 4.0.3. * Bugfix - Bump reva to 2.16.2: [#7512](https://github.com/owncloud/ocis/pull/7512) * Bugfix - Token storage config fixed: [#7546](https://github.com/owncloud/ocis/pull/7546) -* Enhancement - Support spec violating AD FS access token issuer: [#7138](https://github.com/owncloud/ocis/pull/7138) +* Enhancement - Support spec violating AD FS access token issuer: [#7140](https://github.com/owncloud/ocis/pull/7140) * Enhancement - Update web to v7.1.2: [#7798](https://github.com/owncloud/ocis/pull/7798) ## Details @@ -1843,13 +1843,13 @@ The following sections list the changes for 4.0.3. https://github.com/owncloud/ocis/issues/7462 https://github.com/owncloud/ocis/pull/7546 -* Enhancement - Support spec violating AD FS access token issuer: [#7138](https://github.com/owncloud/ocis/pull/7138) +* Enhancement - Support spec violating AD FS access token issuer: [#7140](https://github.com/owncloud/ocis/pull/7140) AD FS `/adfs/.well-known/openid-configuration` has an optional `access_token_issuer` which, in violation of the OpenID Connect spec, takes precedence over `issuer`. - https://github.com/owncloud/ocis/pull/7138 + https://github.com/owncloud/ocis/pull/7140 * Enhancement - Update web to v7.1.2: [#7798](https://github.com/owncloud/ocis/pull/7798) diff --git a/changelog/5.0.0-rc.1_2023-12-27/passwod-policies-renaming.md b/changelog/5.0.0-rc.1_2023-12-27/passwod-policies-renaming.md index 62c188844..8386b9f7f 100644 --- a/changelog/5.0.0-rc.1_2023-12-27/passwod-policies-renaming.md +++ b/changelog/5.0.0-rc.1_2023-12-27/passwod-policies-renaming.md @@ -2,4 +2,4 @@ Enhancement: The password policies change request The variables renaming OCIS-3767 -https://github.com/cs3org/reva/pull/4409 +https://github.com/owncloud/ocis/pull/7264 From 469ba3df0606a1114588eee7136c1357546d022d Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 12 Feb 2024 23:52:53 +0000 Subject: [PATCH 090/112] Automated changelog update [skip ci] --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f01d58e2..19f437c8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1264,8 +1264,8 @@ The following sections list the changes for 5.0.0-rc.1. * Enhancement - Allow configuring additional routes: [#7741](https://github.com/owncloud/ocis/pull/7741) - Allows adding custom routes to the ocis proxy. This enables customs ocis - extensions + Allows adding custom routes to the ocis proxy. This enables custom ocis + extensions. https://github.com/owncloud/ocis/pull/7741 @@ -1823,7 +1823,7 @@ The following sections list the changes for 4.0.3. * Bugfix - Bump reva to 2.16.2: [#7512](https://github.com/owncloud/ocis/pull/7512) * Bugfix - Token storage config fixed: [#7546](https://github.com/owncloud/ocis/pull/7546) -* Enhancement - Support spec violating AD FS access token issuer: [#7140](https://github.com/owncloud/ocis/pull/7140) +* Enhancement - Support spec violating AD FS access token issuer: [#7138](https://github.com/owncloud/ocis/pull/7138) * Enhancement - Update web to v7.1.2: [#7798](https://github.com/owncloud/ocis/pull/7798) ## Details @@ -1843,13 +1843,13 @@ The following sections list the changes for 4.0.3. https://github.com/owncloud/ocis/issues/7462 https://github.com/owncloud/ocis/pull/7546 -* Enhancement - Support spec violating AD FS access token issuer: [#7140](https://github.com/owncloud/ocis/pull/7140) +* Enhancement - Support spec violating AD FS access token issuer: [#7138](https://github.com/owncloud/ocis/pull/7138) AD FS `/adfs/.well-known/openid-configuration` has an optional `access_token_issuer` which, in violation of the OpenID Connect spec, takes precedence over `issuer`. - https://github.com/owncloud/ocis/pull/7140 + https://github.com/owncloud/ocis/pull/7138 * Enhancement - Update web to v7.1.2: [#7798](https://github.com/owncloud/ocis/pull/7798) From 613ce154c51c6cd17e24ff1ffe5e5d82cde70fae Mon Sep 17 00:00:00 2001 From: Viktor Scharf Date: Tue, 13 Feb 2024 06:20:35 +0100 Subject: [PATCH 091/112] [test-only] fix test: secret file drop (#8435) * fix test: secret file drop * Update uploadToPublicLinkShare.feature --- .../expected-failures-API-on-OCIS-storage.md | 4 ---- .../uploadToPublicLinkShare.feature | 17 +---------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/tests/acceptance/expected-failures-API-on-OCIS-storage.md b/tests/acceptance/expected-failures-API-on-OCIS-storage.md index b66665498..9f10eec88 100644 --- a/tests/acceptance/expected-failures-API-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-API-on-OCIS-storage.md @@ -115,10 +115,6 @@ cannot share a folder with create permission - [coreApiSharePublicLink2/copyFromPublicLink.feature:193](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L193) - [coreApiSharePublicLink2/copyFromPublicLink.feature:194](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L194) -#### [Upload-only shares must not overwrite but create a separate file](https://github.com/owncloud/ocis/issues/1267) - -- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:13](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L13) - #### [d:quota-available-bytes in dprop of PROPFIND give wrong response value](https://github.com/owncloud/ocis/issues/8197) - [coreApiWebdavProperties/getQuota.feature:55](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L55) diff --git a/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature b/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature index 518ac93df..0e4a398e5 100644 --- a/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature +++ b/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature @@ -8,22 +8,7 @@ Feature: upload to a public link share Background: Given user "Alice" has been created with default attributes and without skeleton files And user "Alice" has created folder "FOLDER" - - @smokeTest @issue-1267 - Scenario: uploading same file to a public upload-only share multiple times via new API - # The new API does the auto rename in upload-only folders - Given user "Alice" has created a public link share with settings - | path | FOLDER | - | permissions | create | - | password | %public% | - When the public uploads file "test.txt" with password "%public%" and content "test" using the new public WebDAV API - When the public uploads file "test.txt" with password "%public%" and content "test2" using the new public WebDAV API - Then the HTTP status code of responses on all endpoints should be "201" - And the following headers should match these regular expressions - | ETag | /^"[a-f0-9:\.]{1,32}"$/ | - And the content of file "/FOLDER/test.txt" for user "Alice" should be "test" - And the content of file "/FOLDER/test (2).txt" for user "Alice" should be "test2" - + Scenario Outline: uploading file to a public upload-only share using public API that was deleted does not work Given using DAV path From 620195dfe01c586db75c70b74b0cc6f741baacdd Mon Sep 17 00:00:00 2001 From: ownClouders Date: Tue, 13 Feb 2024 06:54:04 +0000 Subject: [PATCH 092/112] [tx] updated from transifex --- .../userlog/pkg/service/l10n/locale/es/LC_MESSAGES/userlog.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/userlog/pkg/service/l10n/locale/es/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/es/LC_MESSAGES/userlog.po index 3d8574bee..8de6830c4 100644 --- a/services/userlog/pkg/service/l10n/locale/es/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/es/LC_MESSAGES/userlog.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-24 00:44+0000\n" +"POT-Creation-Date: 2024-02-13 06:53+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" "Last-Translator: Juan Carlos Garrote, 2023\n" "Language-Team: Spanish (https://app.transifex.com/owncloud-org/teams/6149/es/)\n" From bafb0303871eaeec638e2a8b93edecaed92fc573 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Mon, 12 Feb 2024 17:07:56 +0100 Subject: [PATCH 093/112] add make step to validate the introductionVersion annotation to env-vars Signed-off-by: Christian Richter --- .drone.star | 1 - .make/check-env-var-annotations.sh | 49 +++++++++++++++++++++ Makefile | 4 ++ changelog/unreleased/env-var-annotations.md | 6 +++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100755 .make/check-env-var-annotations.sh create mode 100644 changelog/unreleased/env-var-annotations.md diff --git a/.drone.star b/.drone.star index dc8bcb32a..3f86efd1f 100644 --- a/.drone.star +++ b/.drone.star @@ -1824,7 +1824,6 @@ def docs(): "image": OC_CI_GOLANG, "environment": DRONE_HTTP_PROXY_ENV, "commands": [ - "apk update && apk add asciidoctor" "make -C docs test", ], }, diff --git a/.make/check-env-var-annotations.sh b/.make/check-env-var-annotations.sh new file mode 100755 index 000000000..173b79d03 --- /dev/null +++ b/.make/check-env-var-annotations.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# The following grep will filter out every line containing an `env` annotation +# it will ignore every line that has allready a valid `introductionVersion` annotation +# +# valid examples: +# +# introductionVersion:"pre5.0" +# introductionVersion:"5.0" +# introductionVersion:"4.9.3-rc5" +# introductionVersion:"5.0.1-cheesecake" +# introductionVersion:"5.10.100.15" +# introductionVersion:"0.0" +# +# invalid examples: +# +# introductionVersion:"5.0cheesecake" +# introductionVersion:"5" +# introductionVersion:"5blueberry" +# introductionVersion:"5-lasagna" +# introductionVersion:"4.9.3rc5" + +ERROR=0 + +QUERY_INTRO=$(git grep "env:" -- '*.go' |grep -v -P "introductionVersion:\"([0-9.]{3,}([-].*)?|(pre5\.0))\""|grep -v "_test.go"|grep -v "vendor/") + +RESULTS_INTRO=$(echo "${QUERY_INTRO}"|wc -l) +if [ "${RESULTS_INTRO}" -gt 0 ]; then + echo "===============================================================================================" + echo "The following ${RESULTS_INTRO} files contain an invalid introductionVersion annotation:" + echo "===============================================================================================" + echo "$QUERY_INTRO" + ERROR=1 +fi + +# The following grep will filter out every line containing an `env` annotation +# it will ignore every line that has allready a valid `desc` annotation + +QUERY_DESC=$(git grep "env:" -- '*.go' |grep -v -P "desc:\".{10,}\""|grep -v "_test.go"|grep -v "vendor/") + +RESULTS_DESC=$(echo "${QUERY_DESC}"|wc -l) +if [ "${RESULTS_DESC}" -gt 0 ]; then + echo "===============================================================================================" + echo "The following ${RESULTS_DESC} files contain an invalid description annotation:" + echo "===============================================================================================" + echo "$QUERY_DESC" + ERROR=1 +fi +exit ${ERROR} diff --git a/Makefile b/Makefile index 517f08d4b..c042d3594 100644 --- a/Makefile +++ b/Makefile @@ -176,6 +176,10 @@ docs-generate: $(MAKE) --no-print-directory -C docs docs-generate || exit 1 cp docs/services/general-info/env-var-deltas/*.adoc docs/services/_includes/adoc/env-var-deltas/ +.PHONY: check-env-var-annotations +check-env-var-annotations: + .make/check-env-var-annotations.sh + .PHONY: ci-go-generate ci-go-generate: @for mod in $(OCIS_MODULES); do \ diff --git a/changelog/unreleased/env-var-annotations.md b/changelog/unreleased/env-var-annotations.md new file mode 100644 index 000000000..98be7169b --- /dev/null +++ b/changelog/unreleased/env-var-annotations.md @@ -0,0 +1,6 @@ +Enhancement: Add a make step to validate the env var annotations + +We have added a make step `make validate-env-var-annotations` to validate the env var annotations in to the environment variables. + +https://github.com/owncloud/ocis/pull/8436 +https://github.com/owncloud/ocis/issues/8258 From 1ef091688a329479e0655fbe491520060fea7972 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 13 Feb 2024 08:49:58 +0100 Subject: [PATCH 094/112] move semver regex to a dedicated variable Signed-off-by: Christian Richter --- .make/check-env-var-annotations.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.make/check-env-var-annotations.sh b/.make/check-env-var-annotations.sh index 173b79d03..ffccfb8cf 100755 --- a/.make/check-env-var-annotations.sh +++ b/.make/check-env-var-annotations.sh @@ -22,7 +22,9 @@ ERROR=0 -QUERY_INTRO=$(git grep "env:" -- '*.go' |grep -v -P "introductionVersion:\"([0-9.]{3,}([-].*)?|(pre5\.0))\""|grep -v "_test.go"|grep -v "vendor/") +SEMVER_REGEX="([0-9]|[1-9][0-9]*)(\.([0-9]|[1-9][0-9]*)){1,2}(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?" + +QUERY_INTRO=$(git grep "env:" -- '*.go' |grep -v -P "introductionVersion:\"($SEMVER_REGEX|(pre5\.0))\""|grep -v "_test.go"|grep -v "vendor/") RESULTS_INTRO=$(echo "${QUERY_INTRO}"|wc -l) if [ "${RESULTS_INTRO}" -gt 0 ]; then From 84dc519d10859e7538cb6545ce7cf2addf9d2f02 Mon Sep 17 00:00:00 2001 From: Christian Richter <1058116+dragonchaser@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:09:50 +0000 Subject: [PATCH 095/112] Automated changelog update [skip ci] --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f437c8e..1d55eed3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ The following sections list the changes for unreleased. * Enhancement - Bump reva: [#8340](https://github.com/owncloud/ocis/pull/8340) * Enhancement - Allow sending multiple user ids in one sse event: [#8379](https://github.com/owncloud/ocis/pull/8379) * Enhancement - Allow to skip service listing: [#8408](https://github.com/owncloud/ocis/pull/8408) +* Enhancement - Add a make step to validate the env var annotations: [#8436](https://github.com/owncloud/ocis/pull/8436) ## Details @@ -415,6 +416,14 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/issues/8070 https://github.com/owncloud/ocis/pull/8408 +* Enhancement - Add a make step to validate the env var annotations: [#8436](https://github.com/owncloud/ocis/pull/8436) + + We have added a make step `make validate-env-var-annotations` to validate the + env var annotations in to the environment variables. + + https://github.com/owncloud/ocis/issues/8258 + https://github.com/owncloud/ocis/pull/8436 + # Changelog for [5.0.0-rc.1] (2023-12-27) The following sections list the changes for 5.0.0-rc.1. From 911754fd4297d03f556e47e0ff03c2f64db06b71 Mon Sep 17 00:00:00 2001 From: Sawjan Gurung Date: Tue, 13 Feb 2024 15:58:26 +0545 Subject: [PATCH 096/112] fix: bulky error message for invalid search request (#8444) --- changelog/unreleased/fix-search-error-message.md | 6 ++++++ services/webdav/pkg/service/v0/search.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/fix-search-error-message.md diff --git a/changelog/unreleased/fix-search-error-message.md b/changelog/unreleased/fix-search-error-message.md new file mode 100644 index 000000000..4c65803be --- /dev/null +++ b/changelog/unreleased/fix-search-error-message.md @@ -0,0 +1,6 @@ +Bugfix: Fix search error message + +We fixed an error message returned when the search request is invalid + +https://github.com/owncloud/ocis/pull/8444 +https://github.com/owncloud/ocis/issues/8442 diff --git a/services/webdav/pkg/service/v0/search.go b/services/webdav/pkg/service/v0/search.go index 848b77f22..94c2efbb5 100644 --- a/services/webdav/pkg/service/v0/search.go +++ b/services/webdav/pkg/service/v0/search.go @@ -78,7 +78,7 @@ func (g Webdav) Search(w http.ResponseWriter, r *http.Request) { e := merrors.Parse(err.Error()) switch e.Code { case http.StatusBadRequest: - renderError(w, r, errBadRequest(err.Error())) + renderError(w, r, errBadRequest(e.Detail)) default: renderError(w, r, errInternalError(err.Error())) } From 2ca751390706340403b484b57ad588ac130406e7 Mon Sep 17 00:00:00 2001 From: Sawjan Gurung Date: Tue, 13 Feb 2024 10:14:13 +0000 Subject: [PATCH 097/112] Automated changelog update [skip ci] --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d55eed3a..ae7e3b399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ The following sections list the changes for unreleased. * Bugfix - Fix an error when move: [#8396](https://github.com/owncloud/ocis/pull/8396) * Bugfix - Fix extended env parser: [#8409](https://github.com/owncloud/ocis/pull/8409) * Bugfix - Graph/drives/permission Expiration date update: [#8413](https://github.com/owncloud/ocis/pull/8413) +* Bugfix - Fix search error message: [#8444](https://github.com/owncloud/ocis/pull/8444) * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) * Enhancement - Disable the password policy: [#7985](https://github.com/owncloud/ocis/pull/7985) * Enhancement - Update antivirus service: [#8062](https://github.com/owncloud/ocis/pull/8062) @@ -290,6 +291,13 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/issues/8405 https://github.com/owncloud/ocis/pull/8413 +* Bugfix - Fix search error message: [#8444](https://github.com/owncloud/ocis/pull/8444) + + We fixed an error message returned when the search request is invalid + + https://github.com/owncloud/ocis/issues/8442 + https://github.com/owncloud/ocis/pull/8444 + * Enhancement - Support login page background configuration: [#7674](https://github.com/owncloud/ocis/issues/7674) Introduce a new environment variable `IDP_LOGIN_BACKGROUND_URL` that overrides From 4fc734fc244bcbc5434e4546e1485075a74dcf80 Mon Sep 17 00:00:00 2001 From: Nalem7 <61624650+nabim777@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:28:56 +0545 Subject: [PATCH 098/112] move auto-sync-disable related feature file in separate file (#8443) --- .../apiSharingNg/sharedWithMe.feature | 1026 +--------------- .../sharedWithMeSyncDisabled.feature | 1036 +++++++++++++++++ 2 files changed, 1037 insertions(+), 1025 deletions(-) create mode 100644 tests/acceptance/features/apiSharingNg/sharedWithMeSyncDisabled.feature diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature index 356b6bb63..097f100f9 100644 --- a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature @@ -595,1030 +595,6 @@ Feature: an user gets the resources shared to them """ - Scenario: user lists the file shared with them when auto-sync is disabled - Given user "Alice" has uploaded file with content "to share" to "textfile.txt" - And user "Brian" has disabled the auto-sync share - And user "Alice" has sent the following share invitation: - | resource | textfile.txt | - | space | Personal | - | sharee | Brian | - | shareType | user | - | permissionsRole | Viewer | - When user "Brian" lists the shares shared with him using the Graph API - Then the HTTP status code should be "200" - And the JSON data of the response should match - """ - { - "type": "object", - "required": [ - "value" - ], - "properties": { - "value": { - "type": "array", - "items": { - "type": "object", - "required": [ - "@UI.Hidden", - "@client.synchronize", - "createdBy", - "eTag", - "file", - "id", - "lastModifiedDateTime", - "name", - "parentReference", - "remoteItem", - "size" - ], - "properties": { - "@UI.Hidden": { - "type": "boolean", - "enum": [false] - }, - "@client.synchronize": { - "type": "boolean", - "enum": [false] - }, - "createdBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - }, - "eTag": { - "type": "string", - "pattern": "%etag_pattern%" - }, - "file": { - "type": "object", - "required": ["mimeType"], - "properties": { - "mimeType": { - "type": "string", - "enum": ["text/plain"] - } - } - }, - "id": { - "type": "string", - "pattern": "^%share_id_pattern%$" - }, - "name": { - "type": "string", - "enum": ["textfile.txt"] - }, - "parentReference": { - "type": "object", - "required": ["driveId", "driveType", "id"], - "properties": { - "driveId": { - "type": "string", - "pattern": "^%space_id_pattern%$" - }, - "driveType": { - "type": "string", - "enum": ["virtual"] - }, - "id": { - "type": "string", - "pattern": "^%file_id_pattern%$" - } - } - }, - "remoteItem": { - "type": "object", - "required": [ - "createdBy", - "eTag", - "file", - "id", - "lastModifiedDateTime", - "name", - "parentReference", - "permissions", - "size" - ], - "properties": { - "createdBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName","id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - }, - "eTag": { - "type": "string", - "pattern": "%etag_pattern%" - }, - "file": { - "type": "object", - "required": ["mimeType"], - "properties": { - "mimeType": { - "type": "string", - "enum": ["text/plain"] - } - } - }, - "id": { - "type": "string", - "pattern": "^%file_id_pattern%$" - }, - "name": { - "type": "string", - "enum": ["textfile.txt"] - }, - "parentReference": { - "type": "object", - "required": ["driveId", "driveType"], - "properties": { - "driveId": { - "type": "string", - "pattern": "^%file_id_pattern%$" - }, - "driveType": { - "type": "string", - "enum": ["personal"] - } - } - }, - "permissions": { - "type": "array", - "items": [ - { - "type": "object", - "required": [ - "grantedToV2", - "id", - "invitation", - "roles" - ], - "properties": { - "grantedToV2": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Brian Murphy"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - } - }, - "id": { - "type": "string", - "pattern": "^%permissions_id_pattern%$" - }, - "invitation": { - "type": "object", - "required": ["invitedBy"], - "properties": { - "invitedBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - } - } - }, - "roles": { - "type": "array", - "items": [ - { - "type": "string", - "pattern": "^%role_id_pattern%$" - } - ] - } - } - ] - }, - "size": { - "type": "number", - "enum": [8] - } - } - }, - "size": { - "type": "number", - "enum": [8] - } - } - } - } - } - } - """ - - - Scenario: user lists the folder shared with them when auto-sync is disabled - Given user "Alice" has created folder "folderToShare" - And user "Brian" has disabled the auto-sync share - And user "Alice" has sent the following share invitation: - | resource | folderToShare | - | space | Personal | - | sharee | Brian | - | shareType | user | - | permissionsRole | Viewer | - When user "Brian" lists the shares shared with him using the Graph API - Then the HTTP status code should be "200" - And the JSON data of the response should match - """ - { - "type": "object", - "required": [ - "value" - ], - "properties": { - "value": { - "type": "array", - "items": { - "type": "object", - "required": [ - "@UI.Hidden", - "@client.synchronize", - "createdBy", - "eTag", - "folder", - "id", - "lastModifiedDateTime", - "name", - "parentReference", - "remoteItem" - ], - "properties": { - "@UI.Hidden": { - "type": "boolean", - "enum": [false] - }, - "@client.synchronize": { - "type": "boolean", - "enum": [false] - }, - "createdBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - }, - "eTag": { - "type": "string", - "pattern": "%etag_pattern%" - }, - "id": { - "type": "string", - "pattern": "^%share_id_pattern%$" - }, - "name": { - "type": "string", - "enum": ["folderToShare"] - }, - "parentReference": { - "type": "object", - "required": ["driveId", "driveType", "id"], - "properties": { - "driveId": { - "type": "string", - "pattern": "^%space_id_pattern%$" - }, - "driveType": { - "type": "string", - "enum": ["virtual"] - }, - "id": { - "type": "string", - "pattern": "^%file_id_pattern%$" - } - } - }, - "remoteItem": { - "type": "object", - "required": [ - "createdBy", - "eTag", - "folder", - "id", - "lastModifiedDateTime", - "name", - "parentReference", - "permissions" - ], - "properties": { - "createdBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName","id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - }, - "eTag": { - "type": "string", - "pattern": "%etag_pattern%" - }, - "id": { - "type": "string", - "pattern": "^%file_id_pattern%$" - }, - "name": { - "type": "string", - "enum": ["folderToShare"] - }, - "parentReference": { - "type": "object", - "required": ["driveId", "driveType"], - "properties": { - "driveId": { - "type": "string", - "pattern": "^%file_id_pattern%$" - }, - "driveType": { - "type": "string", - "enum": ["personal"] - } - } - }, - "permissions": { - "type": "array", - "items": [ - { - "type": "object", - "required": [ - "grantedToV2", - "id", - "invitation", - "roles" - ], - "properties": { - "grantedToV2": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Brian Murphy"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - } - }, - "id": { - "type": "string", - "pattern": "^%permissions_id_pattern%$" - }, - "invitation": { - "type": "object", - "required": ["invitedBy"], - "properties": { - "invitedBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - } - } - }, - "roles": { - "type": "array", - "items": [ - { - "type": "string", - "pattern": "^%role_id_pattern%$" - } - ] - } - } - ] - } - } - } - } - } - } - } - } - """ - - - Scenario: group member lists the file shared with them when auto-sync is disabled - And user "Carol" has been created with default attributes and without skeleton files - And group "grp1" has been created - And the following users have been added to the following groups - | username | groupname | - | Brian | grp1 | - | Carol | grp1 | - Given user "Alice" has uploaded file with content "to share" to "textfile.txt" - And user "Brian" has disabled the auto-sync share - And user "Alice" has sent the following share invitation: - | resource | textfile.txt | - | space | Personal | - | sharee | grp1 | - | shareType | group | - | permissionsRole | Viewer | - When user "Brian" lists the shares shared with him using the Graph API - Then the HTTP status code should be "200" - And the JSON data of the response should match - """ - { - "type": "object", - "required": [ - "value" - ], - "properties": { - "value": { - "type": "array", - "items": { - "type": "object", - "required": [ - "@UI.Hidden", - "@client.synchronize", - "createdBy", - "eTag", - "file", - "id", - "lastModifiedDateTime", - "name", - "parentReference", - "remoteItem", - "size" - ], - "properties": { - "@UI.Hidden": { - "type": "boolean", - "enum": [false] - }, - "@client.synchronize": { - "type": "boolean", - "enum": [false] - }, - "createdBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - }, - "eTag": { - "type": "string", - "pattern": "%etag_pattern%" - }, - "file": { - "type": "object", - "required": ["mimeType"], - "properties": { - "mimeType": { - "type": "string", - "enum": ["text/plain"] - } - } - }, - "id": { - "type": "string", - "pattern": "^%share_id_pattern%$" - }, - "name": { - "type": "string", - "enum": ["textfile.txt"] - }, - "parentReference": { - "type": "object", - "required": ["driveId", "driveType", "id"], - "properties": { - "driveId": { - "type": "string", - "pattern": "^%space_id_pattern%$" - }, - "driveType": { - "type": "string", - "enum": ["virtual"] - }, - "id": { - "type": "string", - "pattern": "^%file_id_pattern%$" - } - } - }, - "remoteItem": { - "type": "object", - "required": [ - "createdBy", - "eTag", - "file", - "id", - "lastModifiedDateTime", - "name", - "parentReference", - "permissions", - "size" - ], - "properties": { - "createdBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName","id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - }, - "eTag": { - "type": "string", - "pattern": "%etag_pattern%" - }, - "file": { - "type": "object", - "required": ["mimeType"], - "properties": { - "mimeType": { - "type": "string", - "enum": ["text/plain"] - } - } - }, - "id": { - "type": "string", - "pattern": "^%file_id_pattern%$" - }, - "name": { - "type": "string", - "enum": ["textfile.txt"] - }, - "parentReference": { - "type": "object", - "required": ["driveId", "driveType"], - "properties": { - "driveId": { - "type": "string", - "pattern": "^%file_id_pattern%$" - }, - "driveType": { - "type": "string", - "enum": ["personal"] - } - } - }, - "permissions": { - "type": "array", - "items": [ - { - "type": "object", - "required": [ - "grantedToV2", - "id", - "invitation", - "roles" - ], - "properties": { - "grantedToV2": { - "type": "object", - "required": ["group"], - "properties": { - "group": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["grp1"] - }, - "id": { - "type": "string", - "pattern": "^%group_id_pattern%$" - } - } - } - } - } - }, - "id": { - "type": "string", - "pattern": "^%permissions_id_pattern%$" - }, - "invitation": { - "type": "object", - "required": ["invitedBy"], - "properties": { - "invitedBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - } - } - }, - "roles": { - "type": "array", - "items": [ - { - "type": "string", - "pattern": "^%role_id_pattern%$" - } - ] - } - } - ] - }, - "size": { - "type": "number", - "enum": [8] - } - } - }, - "size": { - "type": "number", - "enum": [8] - } - } - } - } - } - } - """ - - - Scenario: group member lists the folder shared with them when auto-sync is disabled - And user "Carol" has been created with default attributes and without skeleton files - And group "grp1" has been created - And the following users have been added to the following groups - | username | groupname | - | Brian | grp1 | - | Carol | grp1 | - Given user "Alice" has created folder "folderToShare" - And user "Brian" has disabled the auto-sync share - And user "Alice" has sent the following share invitation: - | resource | folderToShare | - | space | Personal | - | sharee | grp1 | - | shareType | group | - | permissionsRole | Viewer | - When user "Brian" lists the shares shared with him using the Graph API - Then the HTTP status code should be "200" - And the JSON data of the response should match - """ - { - "type": "object", - "required": [ - "value" - ], - "properties": { - "value": { - "type": "array", - "items": { - "type": "object", - "required": [ - "@UI.Hidden", - "@client.synchronize", - "createdBy", - "eTag", - "folder", - "id", - "lastModifiedDateTime", - "name", - "parentReference", - "remoteItem" - ], - "properties": { - "@UI.Hidden": { - "type": "boolean", - "enum": [false] - }, - "@client.synchronize": { - "type": "boolean", - "enum": [false] - }, - "createdBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - }, - "eTag": { - "type": "string", - "pattern": "%etag_pattern%" - }, - "id": { - "type": "string", - "pattern": "^%share_id_pattern%$" - }, - "name": { - "type": "string", - "enum": ["folderToShare"] - }, - "parentReference": { - "type": "object", - "required": ["driveId", "driveType", "id"], - "properties": { - "driveId": { - "type": "string", - "pattern": "^%space_id_pattern%$" - }, - "driveType": { - "type": "string", - "enum": ["virtual"] - }, - "id": { - "type": "string", - "pattern": "^%file_id_pattern%$" - } - } - }, - "remoteItem": { - "type": "object", - "required": [ - "createdBy", - "eTag", - "folder", - "id", - "lastModifiedDateTime", - "name", - "parentReference", - "permissions" - ], - "properties": { - "createdBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName","id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - }, - "eTag": { - "type": "string", - "pattern": "%etag_pattern%" - }, - "id": { - "type": "string", - "pattern": "^%file_id_pattern%$" - }, - "name": { - "type": "string", - "enum": ["folderToShare"] - }, - "parentReference": { - "type": "object", - "required": ["driveId", "driveType"], - "properties": { - "driveId": { - "type": "string", - "pattern": "^%file_id_pattern%$" - }, - "driveType": { - "type": "string", - "enum": ["personal"] - } - } - }, - "permissions": { - "type": "array", - "items": [ - { - "type": "object", - "required": [ - "grantedToV2", - "id", - "invitation", - "roles" - ], - "properties": { - "grantedToV2": { - "type": "object", - "required": ["group"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["grp1"] - }, - "id": { - "type": "string", - "pattern": "^%group_id_pattern%$" - } - } - } - } - } - }, - "id": { - "type": "string", - "pattern": "^%permissions_id_pattern%$" - }, - "invitation": { - "type": "object", - "required": ["invitedBy"], - "properties": { - "invitedBy": { - "type": "object", - "required": ["user"], - "properties": { - "user": { - "type": "object", - "required": ["displayName", "id"], - "properties": { - "displayName": { - "type": "string", - "enum": ["Alice Hansen"] - }, - "id": { - "type": "string", - "pattern": "^%user_id_pattern%$" - } - } - } - } - } - } - }, - "roles": { - "type": "array", - "items": [ - { - "type": "string", - "pattern": "^%role_id_pattern%$" - } - ] - } - } - ] - } - } - } - } - } - } - } - } - """ - - Scenario: sharer shares a file to a group and to a user who is in the shared group Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API And user "Alice" has uploaded file with content "hello" to "textfile0.txt" @@ -2974,4 +1950,4 @@ Feature: an user gets the resources shared to them } } } - """ \ No newline at end of file + """ diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMeSyncDisabled.feature b/tests/acceptance/features/apiSharingNg/sharedWithMeSyncDisabled.feature new file mode 100644 index 000000000..470568ab3 --- /dev/null +++ b/tests/acceptance/features/apiSharingNg/sharedWithMeSyncDisabled.feature @@ -0,0 +1,1036 @@ +Feature: listing sharedWithMe when auto-sync is disabled + As a user + I want to get resources shared with me when auto-sync is disabled + So that I can know about what resources I have access to + + https://owncloud.dev/libre-graph-api/#/me.drive/ListSharedWithMe + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Brian | + + + Scenario: user lists the file shared with them when auto-sync is disabled + Given user "Alice" has uploaded file with content "to share" to "textfile.txt" + And user "Brian" has disabled the auto-sync share + And user "Alice" has sent the following share invitation: + | resource | textfile.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem", + "size" + ], + "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [false] + }, + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile.txt"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType", "id"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions", + "size" + ], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile.txt"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + ] + }, + "size": { + "type": "number", + "enum": [8] + } + } + }, + "size": { + "type": "number", + "enum": [8] + } + } + } + } + } + } + """ + + + Scenario: user lists the folder shared with them when auto-sync is disabled + Given user "Alice" has created folder "folderToShare" + And user "Brian" has disabled the auto-sync share + And user "Alice" has sent the following share invitation: + | resource | folderToShare | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem" + ], + "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [false] + }, + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folderToShare"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType", "id"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions" + ], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folderToShare"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + ] + } + } + } + } + } + } + } + } + """ + + + Scenario: group member lists the file shared with them when auto-sync is disabled + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And the following users have been added to the following groups + | username | groupname | + | Brian | grp1 | + | Carol | grp1 | + Given user "Alice" has uploaded file with content "to share" to "textfile.txt" + And user "Brian" has disabled the auto-sync share + And user "Alice" has sent the following share invitation: + | resource | textfile.txt | + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem", + "size" + ], + "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [false] + }, + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile.txt"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType", "id"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions", + "size" + ], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile.txt"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["group"], + "properties": { + "group": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%group_id_pattern%$" + } + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + ] + }, + "size": { + "type": "number", + "enum": [8] + } + } + }, + "size": { + "type": "number", + "enum": [8] + } + } + } + } + } + } + """ + + + Scenario: group member lists the folder shared with them when auto-sync is disabled + And user "Carol" has been created with default attributes and without skeleton files + And group "grp1" has been created + And the following users have been added to the following groups + | username | groupname | + | Brian | grp1 | + | Carol | grp1 | + Given user "Alice" has created folder "folderToShare" + And user "Brian" has disabled the auto-sync share + And user "Alice" has sent the following share invitation: + | resource | folderToShare | + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem" + ], + "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [false] + }, + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folderToShare"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType", "id"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions" + ], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folderToShare"] + }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["group"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%group_id_pattern%$" + } + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + ] + } + } + } + } + } + } + } + } + """ From c7b50b45407404bfaa62ac190165f1ae46f2319e Mon Sep 17 00:00:00 2001 From: Sawjan Gurung Date: Wed, 14 Feb 2024 10:44:04 +0545 Subject: [PATCH 099/112] [tests-only] add API test coverage for trying to search with invalid pattern (#8441) * test(api): test coverage for invalid search pattern * test(api): fix typo --- tests/acceptance/config/behat.yml | 3 +- ...{apiSpaceSearch.feature => search.feature} | 80 ++++++++----------- .../features/bootstrap/SpacesContext.php | 2 +- 3 files changed, 37 insertions(+), 48 deletions(-) rename tests/acceptance/features/apiSearch1/{apiSpaceSearch.feature => search.feature} (78%) diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml index d05231870..b1a025e3c 100644 --- a/tests/acceptance/config/behat.yml +++ b/tests/acceptance/config/behat.yml @@ -231,16 +231,17 @@ default: - "%paths.base%/../features/apiSearch1" context: *common_ldap_suite_context contexts: + - FeatureContext: *common_feature_context_params - SettingsContext: - GraphContext: - SpacesContext: - PublicWebDavContext: - - FeatureContext: *common_feature_context_params - SearchContext: - OCSContext: - SpacesTUSContext: - TagContext: - TUSContext: + - WebDavPropertiesContext: apiSearch2: paths: diff --git a/tests/acceptance/features/apiSearch1/apiSpaceSearch.feature b/tests/acceptance/features/apiSearch1/search.feature similarity index 78% rename from tests/acceptance/features/apiSearch1/apiSpaceSearch.feature rename to tests/acceptance/features/apiSearch1/search.feature index 715ad2c84..92ffe3fbe 100644 --- a/tests/acceptance/features/apiSearch1/apiSpaceSearch.feature +++ b/tests/acceptance/features/apiSearch1/search.feature @@ -3,9 +3,6 @@ Feature: Search I want to search for resources in the space So that I can get them quickly - Note - this feature is run in CI with ACCOUNTS_HASH_DIFFICULTY set to the default for production - See https://github.com/owncloud/ocis/issues/1542 and https://github.com/owncloud/ocis/pull/839 - Background: Given these users have been created with default attributes and without skeleton files: | username | @@ -13,29 +10,12 @@ Feature: Search | Brian | And using spaces DAV path And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API - And user "Alice" has created a space "find data" with the default quota using the Graph API - And user "Alice" has created a folder "folderMain/SubFolder1/subFOLDER2" in space "find data" - And user "Alice" has uploaded a file inside space "find data" with content "some content" to "folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt" + And user "Alice" has created a space "project101" with the default quota using the Graph API + And user "Alice" has created a folder "folderMain/SubFolder1/subFOLDER2" in space "project101" + And user "Alice" has uploaded a file inside space "project101" with content "some content" to "folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt" - Scenario Outline: user can find data from the project space - Given using DAV path - When user "Alice" searches for "*fol*" using the WebDAV API - Then the HTTP status code should be "207" - And the search result should contain "4" entries - And the search result of user "Alice" should contain these entries: - | /folderMain | - | /folderMain/SubFolder1 | - | /folderMain/SubFolder1/subFOLDER2 | - | /folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt | - Examples: - | dav-path-version | - | old | - | new | - | spaces | - - - Scenario Outline: user can only find data that they searched for from the project space + Scenario Outline: user can search items from the project space Given using DAV path When user "Alice" searches for "*SUB*" using the WebDAV API Then the HTTP status code should be "207" @@ -53,9 +33,9 @@ Feature: Search | spaces | - Scenario Outline: user can find data from the shares + Scenario Outline: user can search items from the shares Given using DAV path - And user "Alice" has created a share inside of space "find data" with settings: + And user "Alice" has created a share inside of space "project101" with settings: | path | folderMain | | shareWith | Brian | | role | viewer | @@ -74,9 +54,9 @@ Feature: Search | spaces | - Scenario Outline: user can find hidden file + Scenario Outline: user can search hidden files Given using DAV path - And user "Alice" has created a folder ".space" in space "find data" + And user "Alice" has created a folder ".space" in space "project101" When user "Alice" searches for "*.sp*" using the WebDAV API Then the HTTP status code should be "207" And the search result should contain "1" entries @@ -89,10 +69,10 @@ Feature: Search | spaces | - Scenario Outline: user cannot find pending share + Scenario Outline: user cannot search pending share Given user "Brian" has disabled auto-accepting And using DAV path - And user "Alice" has created a share inside of space "find data" with settings: + And user "Alice" has created a share inside of space "project101" with settings: | path | folderMain | | shareWith | Brian | | role | viewer | @@ -110,9 +90,9 @@ Feature: Search | spaces | - Scenario Outline: user cannot find declined share + Scenario Outline: user cannot search declined share Given using DAV path - And user "Alice" has created a share inside of space "find data" with settings: + And user "Alice" has created a share inside of space "project101" with settings: | path | folderMain | | shareWith | Brian | | role | viewer | @@ -131,9 +111,9 @@ Feature: Search | spaces | - Scenario Outline: user cannot find deleted folder + Scenario Outline: user cannot search deleted items Given using DAV path - And user "Alice" has removed the folder "folderMain" from space "find data" + And user "Alice" has removed the folder "folderMain" from space "project101" When user "Alice" searches for "*folderMain*" using the WebDAV API Then the HTTP status code should be "207" And the search result should contain "0" entries @@ -144,12 +124,12 @@ Feature: Search | spaces | - Scenario Outline: user can find project space by name + Scenario Outline: user can search project space by name Given using DAV path - When user "Alice" searches for '"*find data*"' using the WebDAV API + When user "Alice" searches for '*project101*' using the WebDAV API Then the HTTP status code should be "207" And the search result should contain "1" entries - And for user "Alice" the search result should contain space "find data" + And for user "Alice" the search result should contain space "project101" Examples: | dav-path-version | | old | @@ -159,7 +139,7 @@ Feature: Search Scenario Outline: user can search inside folder in space Given using DAV path - When user "Alice" searches for "*folder*" inside folder "/folderMain" in space "find data" using the WebDAV API + When user "Alice" searches for "*folder*" inside folder "/folderMain" in space "project101" using the WebDAV API Then the HTTP status code should be "207" And the search result should contain "3" entries And the search result of user "Alice" should contain only these entries: @@ -177,7 +157,7 @@ Feature: Search Scenario Outline: search inside folder in shares Given using DAV path - And user "Alice" has created a share inside of space "find data" with settings: + And user "Alice" has created a share inside of space "project101" with settings: | path | folderMain | | shareWith | Brian | | role | viewer | @@ -277,16 +257,24 @@ Feature: Search Scenario Outline: search resources using different search patterns (KQL feature) Given using spaces DAV path - And user "Alice" has created a folder "subfolder" in space "find data" + And user "Alice" has created a folder "subfolder" in space "project101" When user "Alice" searches for '' using the WebDAV API Then the HTTP status code should be "207" And the search result should contain "1" entries And the search result of user "Alice" should contain these entries: | | Examples: - | description | pattern | search-result | - | starts with | fold* | /folderMain | - | ends with | *der1 | /folderMain/SubFolder1 | - | strict search | subfolder | /subfolder | - | using patern "name:" | name:*der2 | /folderMain/SubFolder1/subFOLDER2 | - | using the pattern "name:" where the value is in quotes | name:"*der2" | /folderMain/SubFolder1/subFOLDER2 | + | pattern | search-result | description | + | fold* | /folderMain | starts with | + | *der1 | /folderMain/SubFolder1 | ends with | + | subfolder | /subfolder | exact search | + | name:*der2 | /folderMain/SubFolder1/subFOLDER2 | patern 'name:'' | + | name:"*der2" | /folderMain/SubFolder1/subFOLDER2 | pattern 'name:""' (with quotes) | + + @issue-7812 @issue-8442 + Scenario: try to search with invalid patterns + Given using spaces DAV path + And user "Alice" has uploaded file with content "test file" to "testFile.txt" + When user "Alice" searches for 'AND mediatype:document' using the WebDAV API + Then the HTTP status code should be "400" + And the value of the item "/d:error/s:message" in the response should be "error: bad request: the expression can't begin from a binary operator: 'AND'" diff --git a/tests/acceptance/features/bootstrap/SpacesContext.php b/tests/acceptance/features/bootstrap/SpacesContext.php index de0020bb5..ea1197869 100644 --- a/tests/acceptance/features/bootstrap/SpacesContext.php +++ b/tests/acceptance/features/bootstrap/SpacesContext.php @@ -347,7 +347,7 @@ class SpacesContext implements Context { if ($folderName === $space["name"]) { $folderName = ''; } - $fullUrl = $this->baseUrl . $this->davSpacesUrl . $space["id"] . "/" . $folderName; + $fullUrl = $space["root"]["webDavUrl"] . "/" . $folderName; $response = HttpRequestHelper::sendRequest( $fullUrl, $this->featureContext->getStepLineRef(), From 421ce403b53ae1912f3c44fab2b4564f23cdea23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Feb 2024 06:38:38 +0000 Subject: [PATCH 100/112] build(deps): bump google.golang.org/grpc from 1.61.0 to 1.61.1 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.61.0 to 1.61.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.61.0...v1.61.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- .../grpc/internal/transport/controlbuf.go | 5 ++-- .../grpc/internal/transport/http2_client.go | 8 ++++++- .../grpc/internal/transport/http2_server.go | 23 ++++++++++++++++--- vendor/google.golang.org/grpc/version.go | 2 +- vendor/modules.txt | 2 +- 7 files changed, 34 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index eed8672f9..eaff57a69 100644 --- a/go.mod +++ b/go.mod @@ -103,7 +103,7 @@ require ( golang.org/x/term v0.17.0 golang.org/x/text v0.14.0 google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe - google.golang.org/grpc v1.61.0 + google.golang.org/grpc v1.61.1 google.golang.org/protobuf v1.32.0 gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 2e1ec30f1..5141a427f 100644 --- a/go.sum +++ b/go.sum @@ -2935,8 +2935,8 @@ google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGO google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e h1:m7aQHHqd0q89mRwhwS9Bx2rjyl/hsFAeta+uGrHsQaU= google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e/go.mod h1:gID3PKrg7pWKntu9Ss6zTLJ0ttC0X9IHgREOCZwbCVU= diff --git a/vendor/google.golang.org/grpc/internal/transport/controlbuf.go b/vendor/google.golang.org/grpc/internal/transport/controlbuf.go index b330ccedc..83c382982 100644 --- a/vendor/google.golang.org/grpc/internal/transport/controlbuf.go +++ b/vendor/google.golang.org/grpc/internal/transport/controlbuf.go @@ -535,8 +535,8 @@ const minBatchSize = 1000 // size is too low to give stream goroutines a chance to fill it up. // // Upon exiting, if the error causing the exit is not an I/O error, run() -// flushes and closes the underlying connection. Otherwise, the connection is -// left open to allow the I/O error to be encountered by the reader instead. +// flushes the underlying connection. The connection is always left open to +// allow different closing behavior on the client and server. func (l *loopyWriter) run() (err error) { defer func() { if l.logger.V(logLevel) { @@ -544,7 +544,6 @@ func (l *loopyWriter) run() (err error) { } if !isIOError(err) { l.framer.writer.Flush() - l.conn.Close() } l.cbuf.finish() }() diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_client.go b/vendor/google.golang.org/grpc/internal/transport/http2_client.go index c33ac5961..eff879964 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_client.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_client.go @@ -451,7 +451,13 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts } go func() { t.loopy = newLoopyWriter(clientSide, t.framer, t.controlBuf, t.bdpEst, t.conn, t.logger) - t.loopy.run() + if err := t.loopy.run(); !isIOError(err) { + // Immediately close the connection, as the loopy writer returns + // when there are no more active streams and we were draining (the + // server sent a GOAWAY). For I/O errors, the reader will hit it + // after draining any remaining incoming data. + t.conn.Close() + } close(t.writerDone) }() return t, nil diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_server.go b/vendor/google.golang.org/grpc/internal/transport/http2_server.go index f6bac0e8a..a206e2eef 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_server.go @@ -322,8 +322,24 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport, go func() { t.loopy = newLoopyWriter(serverSide, t.framer, t.controlBuf, t.bdpEst, t.conn, t.logger) t.loopy.ssGoAwayHandler = t.outgoingGoAwayHandler - t.loopy.run() + err := t.loopy.run() close(t.loopyWriterDone) + if !isIOError(err) { + // Close the connection if a non-I/O error occurs (for I/O errors + // the reader will also encounter the error and close). Wait 1 + // second before closing the connection, or when the reader is done + // (i.e. the client already closed the connection or a connection + // error occurred). This avoids the potential problem where there + // is unread data on the receive side of the connection, which, if + // closed, would lead to a TCP RST instead of FIN, and the client + // encountering errors. For more info: + // https://github.com/grpc/grpc-go/issues/5358 + select { + case <-t.readerDone: + case <-time.After(time.Second): + } + t.conn.Close() + } }() go t.keepalive() return t, nil @@ -609,8 +625,8 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade // traceCtx attaches trace to ctx and returns the new context. func (t *http2Server) HandleStreams(ctx context.Context, handle func(*Stream)) { defer func() { - <-t.loopyWriterDone close(t.readerDone) + <-t.loopyWriterDone }() for { t.controlBuf.throttle() @@ -1329,6 +1345,7 @@ func (t *http2Server) outgoingGoAwayHandler(g *goAway) (bool, error) { if err := t.framer.fr.WriteGoAway(sid, g.code, g.debugData); err != nil { return false, err } + t.framer.writer.Flush() if retErr != nil { return false, retErr } @@ -1349,7 +1366,7 @@ func (t *http2Server) outgoingGoAwayHandler(g *goAway) (bool, error) { return false, err } go func() { - timer := time.NewTimer(time.Minute) + timer := time.NewTimer(5 * time.Second) defer timer.Stop() select { case <-t.drainEvent.Done(): diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index 1ad1ba2ad..f1aec4c0a 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.61.0" +const Version = "1.61.1" diff --git a/vendor/modules.txt b/vendor/modules.txt index 0700f1c62..5cdac4973 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2193,7 +2193,7 @@ google.golang.org/genproto/googleapis/api/httpbody ## explicit; go 1.19 google.golang.org/genproto/googleapis/rpc/errdetails google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.61.0 +# google.golang.org/grpc v1.61.1 ## explicit; go 1.19 google.golang.org/grpc google.golang.org/grpc/attributes From e0342492a1ae24742a1e92d03096bd2c69be81a3 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Wed, 14 Feb 2024 14:57:10 +0100 Subject: [PATCH 101/112] docs: add changelog from stable-4.0 --- changelog/4.0.5_2023-12-21/add-trach-bin-cli.md | 6 ++++++ .../fix-auth-allow-empty-cred-chain.md | 11 +++++++++++ changelog/4.0.5_2023-12-21/fix-traces.md | 5 +++++ .../4.0.5_2023-12-21/fix-upload-session-purging.md | 5 +++++ changelog/4.0.6_2024-02-07/fix-RED-metrics.md | 11 +++++++++++ changelog/4.0.6_2024-02-07/fix-signed-url-expiry.md | 5 +++++ 6 files changed, 43 insertions(+) create mode 100644 changelog/4.0.5_2023-12-21/add-trach-bin-cli.md create mode 100644 changelog/4.0.5_2023-12-21/fix-auth-allow-empty-cred-chain.md create mode 100644 changelog/4.0.5_2023-12-21/fix-traces.md create mode 100644 changelog/4.0.5_2023-12-21/fix-upload-session-purging.md create mode 100644 changelog/4.0.6_2024-02-07/fix-RED-metrics.md create mode 100644 changelog/4.0.6_2024-02-07/fix-signed-url-expiry.md diff --git a/changelog/4.0.5_2023-12-21/add-trach-bin-cli.md b/changelog/4.0.5_2023-12-21/add-trach-bin-cli.md new file mode 100644 index 000000000..91485a28f --- /dev/null +++ b/changelog/4.0.5_2023-12-21/add-trach-bin-cli.md @@ -0,0 +1,6 @@ +Enhancement: Add cli commands for trash-binq + +We added the `list` and `restore` commands to the trash-bin items to the CLI + +https://github.com/owncloud/ocis/pull/7936 +https://github.com/owncloud/ocis/issues/7845 diff --git a/changelog/4.0.5_2023-12-21/fix-auth-allow-empty-cred-chain.md b/changelog/4.0.5_2023-12-21/fix-auth-allow-empty-cred-chain.md new file mode 100644 index 000000000..d1eff1fe9 --- /dev/null +++ b/changelog/4.0.5_2023-12-21/fix-auth-allow-empty-cred-chain.md @@ -0,0 +1,11 @@ +Bugfix: fix reva config of frontend service to avoid misleading error logs + +We set an empty Credentials chain for the frontend service now. In ocis all +non-reva token authentication is handled by the proxy. This avoids irritating +error messages about the missing 'auth-bearer' service. + +https://github.com/owncloud/ocis/pull/7934 +https://github.com/owncloud/ocis/pull/7453 +https://github.com/cs3org/reva/pull/4396 +https://github.com/cs3org/reva/pull/4241 +https://github.com/owncloud/ocis/issues/6692 diff --git a/changelog/4.0.5_2023-12-21/fix-traces.md b/changelog/4.0.5_2023-12-21/fix-traces.md new file mode 100644 index 000000000..acdb4d527 --- /dev/null +++ b/changelog/4.0.5_2023-12-21/fix-traces.md @@ -0,0 +1,5 @@ +Bugfix: Fix trace ids + +We changed the default tracing to produce non-empty traceids and fixed a problem where traces got disconnected further down the stack. + +https://github.com/owncloud/ocis/pull/8026 diff --git a/changelog/4.0.5_2023-12-21/fix-upload-session-purging.md b/changelog/4.0.5_2023-12-21/fix-upload-session-purging.md new file mode 100644 index 000000000..5ff837037 --- /dev/null +++ b/changelog/4.0.5_2023-12-21/fix-upload-session-purging.md @@ -0,0 +1,5 @@ +Bugfix: Do not purge expired upload sessions that are still postprocessing + +https://github.com/owncloud/ocis/pull/7941 +https://github.com/owncloud/ocis/pull/7859 +https://github.com/owncloud/ocis/pull/7958 \ No newline at end of file diff --git a/changelog/4.0.6_2024-02-07/fix-RED-metrics.md b/changelog/4.0.6_2024-02-07/fix-RED-metrics.md new file mode 100644 index 000000000..89cf681e2 --- /dev/null +++ b/changelog/4.0.6_2024-02-07/fix-RED-metrics.md @@ -0,0 +1,11 @@ +Bugfix: Fix RED metrics on the metrics endpoint + +We connected some metrics to the metrics endpoint to support the RED method for monitoring microservices. + +- Request Rate: The number of requests per second. The total count of requests is available under `ocis_proxy_requests_total`. +- Error Rate: The number of failed requests per second. The total count of failed requests is available under `ocis_proxy_errors_total`. +- Duration: The amount of time each request takes. The duration of all requests is available under `ocis_proxy_request_duration_seconds`. This is a histogram metric, so it also provides information about the distribution of request durations. + +The metrics are available under the following paths: `PROXY_DEBUG_ADDR/metrics` in a prometheus compatible format and maybe secured by `PROXY_DEBUG_TOKEN`. + +https://github.com/owncloud/ocis/pull/7994 diff --git a/changelog/4.0.6_2024-02-07/fix-signed-url-expiry.md b/changelog/4.0.6_2024-02-07/fix-signed-url-expiry.md new file mode 100644 index 000000000..9b0cdf3da --- /dev/null +++ b/changelog/4.0.6_2024-02-07/fix-signed-url-expiry.md @@ -0,0 +1,5 @@ +Bugfix: signed url verification + +Signed urls now expire properly + +https://github.com/owncloud/ocis/pull/8385 From 331e92e25c89f7c25c0af385cc3ee83cb0acae10 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Wed, 14 Feb 2024 15:01:09 +0100 Subject: [PATCH 102/112] "browser": "./src/caf.js", fix: keep .drone.star and released cd instances as in master --- .drone.star | 53 ++++++++++--------- .../ocis_keycloak/released.yml | 2 +- .../ocis_ldap/released.yml | 2 +- .../ocis_traefik/released.yml | 2 +- .../ocis_wopi/released.yml | 2 +- ocis-pkg/version/version.go | 2 +- sonar-project.properties | 2 +- 7 files changed, 35 insertions(+), 30 deletions(-) diff --git a/.drone.star b/.drone.star index 9908c5a02..3f86efd1f 100644 --- a/.drone.star +++ b/.drone.star @@ -302,7 +302,7 @@ def cachePipeline(name, steps): "steps": steps, "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/tags/**", "refs/pull/**", ], @@ -362,6 +362,7 @@ def getGoBinForTesting(ctx): cacheGoBin(), "trigger": { "ref": [ + "refs/heads/master", "refs/heads/stable-*", "refs/pull/**", ], @@ -500,7 +501,7 @@ def testOcis(ctx): "steps": steps, "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], }, @@ -556,7 +557,7 @@ def buildOcisBinaryForTesting(ctx): rebuildBuildArtifactCache(ctx, "ocis-binary-amd64", "ocis/bin"), "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], }, @@ -643,7 +644,7 @@ def uploadScanResults(ctx): ], "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], "status": [ @@ -757,7 +758,7 @@ def codestyle(ctx): "depends_on": [], "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", "refs/tags/**", ], @@ -811,7 +812,7 @@ def localApiTestPipeline(ctx): "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], }, @@ -871,7 +872,7 @@ def cs3ApiTests(ctx, storage, accounts_hash_difficulty = 4): "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], }, @@ -978,7 +979,7 @@ def wopiValidatorTests(ctx, storage, accounts_hash_difficulty = 4): "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], }, @@ -1027,7 +1028,7 @@ def coreApiTests(ctx, part_number = 1, number_of_parts = 1, storage = "ocis", ac "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], }, @@ -1151,7 +1152,7 @@ def uiTestPipeline(ctx, filterTags, runPart = 1, numberOfParts = 1, storage = "o "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)] + buildWebCache(ctx)), "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], }, @@ -1175,7 +1176,7 @@ def e2eTests(ctx): e2e_trigger = { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/tags/**", "refs/pull/**", ], @@ -1384,7 +1385,7 @@ def dockerRelease(ctx, arch): "depends_on": depends_on, "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/tags/v*", "refs/pull/**", ], @@ -1461,6 +1462,7 @@ def binaryRelease(ctx, name): ], "when": { "ref": [ + "refs/heads/master", "refs/tags/v*", ], }, @@ -1471,6 +1473,7 @@ def binaryRelease(ctx, name): "settings": settings, "when": { "ref": [ + "refs/heads/master", "refs/tags/v*", ], }, @@ -1513,7 +1516,7 @@ def binaryRelease(ctx, name): "depends_on": depends_on, "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/tags/v*", "refs/pull/**", ], @@ -1605,6 +1608,7 @@ def licenseCheck(ctx): "settings": settings, "when": { "ref": [ + "refs/heads/master", "refs/tags/v*", ], }, @@ -1614,7 +1618,7 @@ def licenseCheck(ctx): "image": OC_CI_GOLANG, "environment": DRONE_HTTP_PROXY_ENV, "commands": [ - "make changelog CHANGELOG_VERSION=%s" % ctx.build.ref.replace("refs/tags/v", ""), + "make changelog CHANGELOG_VERSION=%s" % ctx.build.ref.replace("refs/tags/v", "").split("-")[0], ], "when": { "ref": [ @@ -1646,6 +1650,7 @@ def licenseCheck(ctx): ], "trigger": { "ref": [ + "refs/heads/master", "refs/tags/v*", "refs/pull/**", ], @@ -1681,6 +1686,7 @@ def releaseDockerManifest(): ], "trigger": { "ref": [ + "refs/heads/master", "refs/tags/v*", ], }, @@ -1727,7 +1733,7 @@ def changelog(): "push", ], "message": "Automated changelog update [skip ci]", - "branch": "stable-5.0", + "branch": "master", "author_email": "devops@owncloud.com", "author_name": "ownClouders", "netrc_machine": "github.com", @@ -1749,7 +1755,7 @@ def changelog(): ], "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], }, @@ -1783,6 +1789,7 @@ def releaseDockerReadme(ctx): ], "trigger": { "ref": [ + "refs/heads/master", "refs/tags/v*", ], }, @@ -1832,7 +1839,7 @@ def docs(): }, "pages_directory": "docs/hugo/content/", "copy_contents": "true", - "target_branch": "docs-stable-5.0", + "target_branch": "docs", "delete": "true", }, "when": { @@ -1854,7 +1861,7 @@ def docs(): ], "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], }, @@ -1920,7 +1927,7 @@ def notify(): "depends_on": [], "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/heads/release*", "refs/tags/**", ], @@ -2225,6 +2232,7 @@ def deploy(ctx, config, rebuild): ], "trigger": { "ref": [ + "refs/heads/master", "refs/tags/v*", ], }, @@ -2329,7 +2337,7 @@ def genericCachePurge(flush_path): ], "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], "status": [ @@ -2581,7 +2589,7 @@ def litmus(ctx, storage): "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ - "refs/heads/stable-*", + "refs/heads/master", "refs/pull/**", ], }, @@ -2887,8 +2895,5 @@ def k6LoadTests(ctx): "event": [ "cron", ], - "ref": [ - "refs/heads/master", - ], }, }] diff --git a/deployments/continuous-deployment-config/ocis_keycloak/released.yml b/deployments/continuous-deployment-config/ocis_keycloak/released.yml index 8a2d8e3b9..7b4745672 100644 --- a/deployments/continuous-deployment-config/ocis_keycloak/released.yml +++ b/deployments/continuous-deployment-config/ocis_keycloak/released.yml @@ -32,7 +32,7 @@ env: INSECURE: "false" TRAEFIK_ACME_MAIL: wkloucek@owncloud.com - OCIS_DOCKER_TAG: 5.0.0-rc.3 + OCIS_DOCKER_TAG: 4.0.0 OCIS_DOMAIN: ocis.ocis-keycloak.released.owncloud.works KEYCLOAK_DOMAIN: keycloak.ocis-keycloak.released.owncloud.works COMPOSE_FILE: docker-compose.yml:monitoring_tracing/docker-compose-additions.yml diff --git a/deployments/continuous-deployment-config/ocis_ldap/released.yml b/deployments/continuous-deployment-config/ocis_ldap/released.yml index d942ee04f..abba2b35f 100644 --- a/deployments/continuous-deployment-config/ocis_ldap/released.yml +++ b/deployments/continuous-deployment-config/ocis_ldap/released.yml @@ -32,7 +32,7 @@ env: INSECURE: "false" TRAEFIK_ACME_MAIL: wkloucek@owncloud.com - OCIS_DOCKER_TAG: 5.0.0-rc.3 + OCIS_DOCKER_TAG: 4.0.0 OCIS_DOMAIN: ocis.ocis-ldap.released.owncloud.works LDAP_MANAGER_DOMAIN: ldap.ocis-ldap.released.owncloud.works COMPOSE_FILE: docker-compose.yml:monitoring_tracing/docker-compose-additions.yml diff --git a/deployments/continuous-deployment-config/ocis_traefik/released.yml b/deployments/continuous-deployment-config/ocis_traefik/released.yml index c34ef74c9..befd8657b 100644 --- a/deployments/continuous-deployment-config/ocis_traefik/released.yml +++ b/deployments/continuous-deployment-config/ocis_traefik/released.yml @@ -32,7 +32,7 @@ env: INSECURE: "false" TRAEFIK_ACME_MAIL: wkloucek@owncloud.com - OCIS_DOCKER_TAG: 5.0.0-rc.3 + OCIS_DOCKER_TAG: 4.0.0 OCIS_DOMAIN: ocis.ocis-traefik.released.owncloud.works DEMO_USERS: "true" INBUCKET_DOMAIN: mail.ocis-traefik.released.owncloud.works diff --git a/deployments/continuous-deployment-config/ocis_wopi/released.yml b/deployments/continuous-deployment-config/ocis_wopi/released.yml index 4afc72352..c8521d340 100644 --- a/deployments/continuous-deployment-config/ocis_wopi/released.yml +++ b/deployments/continuous-deployment-config/ocis_wopi/released.yml @@ -32,7 +32,7 @@ env: INSECURE: "false" TRAEFIK_ACME_MAIL: wkloucek@owncloud.com - OCIS_DOCKER_TAG: 5.0.0-rc.3 + OCIS_DOCKER_TAG: 4.0.0 OCIS_DOMAIN: ocis.ocis-wopi.released.owncloud.works COMPANION_DOMAIN: companion.ocis-wopi.released.owncloud.works COMPANION_IMAGE: owncloud/uppy-companion:3.12.13-owncloud diff --git a/ocis-pkg/version/version.go b/ocis-pkg/version/version.go index fe27cc1f6..86a21cac1 100644 --- a/ocis-pkg/version/version.go +++ b/ocis-pkg/version/version.go @@ -16,7 +16,7 @@ var ( // LatestTag is the latest released version plus the dev meta version. // Will be overwritten by the release pipeline // Needs a manual change for every tagged release - LatestTag = "5.0.0-rc.4+dev" + LatestTag = "5.1.0-prealpha+dev" // Date indicates the build date. // This has been removed, it looks like you can only replace static strings with recent go versions diff --git a/sonar-project.properties b/sonar-project.properties index e9383ea45..c4ada13e4 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ sonar.projectKey=owncloud_ocis sonar.organization=owncloud-1 sonar.projectName=ocis -sonar.projectVersion=5.0.0-rc.4 +sonar.projectVersion=5.1.0-prealpha sonar.host.url=https://sonarcloud.io # ===================================================== From 3563346a259dd4f9a94a7b386a9add5c4e20bea4 Mon Sep 17 00:00:00 2001 From: mmattel Date: Wed, 14 Feb 2024 15:23:10 +0100 Subject: [PATCH 103/112] [docs-only] Fixes doc adoc table templates --- docs/templates/ADOC_extended.tmpl | 3 +-- docs/templates/ADOC_global.tmpl | 9 --------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/docs/templates/ADOC_extended.tmpl b/docs/templates/ADOC_extended.tmpl index 16c558f0d..829570039 100644 --- a/docs/templates/ADOC_extended.tmpl +++ b/docs/templates/ADOC_extended.tmpl @@ -2,13 +2,12 @@ [caption=] .Environment variables with extended scope not included in a service -[width="100%",cols="~,~,~,~",options="header"] +[width="100%",cols="35%,10%,18%,~"",options="header"] |=== | Name | Type | Default Value | Description - {{- range .Variables}} {{- if .Ignore }} diff --git a/docs/templates/ADOC_global.tmpl b/docs/templates/ADOC_global.tmpl index 5c9d02a24..424c01048 100644 --- a/docs/templates/ADOC_global.tmpl +++ b/docs/templates/ADOC_global.tmpl @@ -1,12 +1,5 @@ // collected through docs/helpers/adoc-generator.go.tmpl -Note that some global environment variables have been deprecated and replaced by a new one starting with `OCIS_` for naming consistency: - -* All envvars starting with `LDAP_` -* All envvars starting with `IDM_` except `IDM_CREATE_DEMO_USERS` -* The following envvars: `OCIS_REVA_GATEWAY`, `STORAGE_TRANSFER_SECRET`, `OCIS_ASYNC_UPLOADS`, `USERLOG_MACHINE_AUTH_API_KEY`. -* Note that `WEB_UI_CONFIG_FILE` is not a global envar and will dropped from the list in a later release. - [.landscape] [caption=] .Environment variables with global scope available in multiple services @@ -17,9 +10,7 @@ Note that some global environment variables have been deprecated and replaced by | Type | Default Value | Description - {{ range . }} - a| `{{ .Name }}` a| [subs=attributes+] From 4a13517d5bb56a2a57f89b872ead5397cad024f9 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Wed, 14 Feb 2024 15:46:30 +0100 Subject: [PATCH 104/112] Update changelog/4.0.5_2023-12-21/add-trach-bin-cli.md Co-authored-by: Martin --- changelog/4.0.5_2023-12-21/add-trach-bin-cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/4.0.5_2023-12-21/add-trach-bin-cli.md b/changelog/4.0.5_2023-12-21/add-trach-bin-cli.md index 91485a28f..c2c26aaa2 100644 --- a/changelog/4.0.5_2023-12-21/add-trach-bin-cli.md +++ b/changelog/4.0.5_2023-12-21/add-trach-bin-cli.md @@ -1,4 +1,4 @@ -Enhancement: Add cli commands for trash-binq +Enhancement: Add cli commands for trash-bin We added the `list` and `restore` commands to the trash-bin items to the CLI From 5911f354beb9432f03106816dc46e23b9ee6a206 Mon Sep 17 00:00:00 2001 From: ownClouders Date: Thu, 15 Feb 2024 01:15:35 +0000 Subject: [PATCH 105/112] [tx] updated from transifex --- .../pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po index 745f57977..d572b7c67 100644 --- a/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po +++ b/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po @@ -5,17 +5,17 @@ # # Translators: # Michael Barz , 2023 -# Edith Parzefall, 2023 # Jürgen Weigert , 2023 +# Edith Parzefall, 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2024-01-28 00:35+0000\n" +"POT-Creation-Date: 2024-02-15 01:14+0000\n" "PO-Revision-Date: 2023-03-15 08:28+0000\n" -"Last-Translator: Jürgen Weigert , 2023\n" +"Last-Translator: Edith Parzefall, 2024\n" "Language-Team: German (Germany) (https://app.transifex.com/owncloud-org/teams/6149/de_DE/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -68,7 +68,7 @@ msgstr "Ressource geteilt" #: pkg/service/templates.go:49 msgid "Resource unshared" -msgstr "Resource nicht mehr geteilt" +msgstr "Ressource nicht mehr geteilt" #: pkg/service/templates.go:54 msgid "Share expired" From 2a4c18e48555129a2f66d772813bbd8f65bece48 Mon Sep 17 00:00:00 2001 From: Nalem7 <61624650+nabim777@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:20:57 +0545 Subject: [PATCH 106/112] add tests to list space file shared with them when sync is disabled (#8406) --- .../sharedWithMeSyncDisabled.feature | 459 ++++++++++++++++++ 1 file changed, 459 insertions(+) diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMeSyncDisabled.feature b/tests/acceptance/features/apiSharingNg/sharedWithMeSyncDisabled.feature index 470568ab3..2e2a8ad43 100644 --- a/tests/acceptance/features/apiSharingNg/sharedWithMeSyncDisabled.feature +++ b/tests/acceptance/features/apiSharingNg/sharedWithMeSyncDisabled.feature @@ -1034,3 +1034,462 @@ Feature: listing sharedWithMe when auto-sync is disabled } } """ + + + Scenario: user lists file shares shared with them from project-space when auto-sync is disabled + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "projectSpace" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "projectSpace" with content "to share" to "textfile.txt" + And user "Brian" has disabled the auto-sync share + And user "Alice" has sent the following share invitation: + | resource | textfile.txt | + | space | projectSpace | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem", + "size" + ], + "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [false] + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile.txt"] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType", + "id" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions", + "size" + ], + "properties": { + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile.txt"] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["project"] + } + } + }, + "permissions": { + "type": "array", + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + ] + }, + "size": { + "type": "number", + "enum": [8] + } + } + }, + "size": { + "type": "number", + "enum": [8] + } + } + } + } + } + } + """ + + + Scenario: user lists folder shares shared with them from project-space when auto-sync is disabled + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "projectSpace" with the default quota using the Graph API + And user "Alice" has created a folder "folderToShare" in space "projectSpace" + And user "Brian" has disabled the auto-sync share + And user "Alice" has sent the following share invitation: + | resource | folderToShare | + | space | projectSpace | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem" + ], + "properties": { + "@UI.Hidden": { + "type": "boolean", + "enum": [false] + }, + "@client.synchronize": { + "type": "boolean", + "enum": [false] + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "folder": { + "type": "object" + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folderToShare"] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType", + "id" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["virtual"] + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions" + ], + "properties": { + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folderToShare"] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "driveType": { + "type": "string", + "enum": ["project"] + } + } + }, + "permissions": { + "type": "array", + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Brian Murphy"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + ] + } + } + } + } + } + } + } + } + """ From faa8bdbc95c6e60279f44d9509a7d1de6e12ed09 Mon Sep 17 00:00:00 2001 From: nirajacharya2 <122071597+nirajacharya2@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:10:52 +0545 Subject: [PATCH 107/112] adding tests for shareWithMe endpoint when resource is shared with a group (#8388) --- .../apiSharingNg/sharedWithMe.feature | 970 ++++++++++++++++++ 1 file changed, 970 insertions(+) mode change 100644 => 100755 tests/acceptance/features/apiSharingNg/sharedWithMe.feature diff --git a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature old mode 100644 new mode 100755 index 097f100f9..b0bee3d76 --- a/tests/acceptance/features/apiSharingNg/sharedWithMe.feature +++ b/tests/acceptance/features/apiSharingNg/sharedWithMe.feature @@ -1951,3 +1951,973 @@ Feature: an user gets the resources shared to them } } """ + + + Scenario: user lists file shared with them in a group from sharer's personal space + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has uploaded file with content "hello" to "textfile0.txt" + And user "Alice" has created a group "grp1" using the Graph API + And user "Brian" has been added to group "grp1" + And user "Alice" has sent the following share invitation: + | resource | textfile0.txt | + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem", + "size" + ], + "properties": { + "@UI.Hidden":{ + "type": "boolean", + "enum": [false] + }, + "@client.synchronize":{ + "type": "boolean", + "enum": [true] + }, + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": [ + "textfile0.txt" + ] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType", + "id" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType" : { + "type": "string", + "enum": ["virtual"] + }, + "id" : { + "type": "string", + "pattern": "%space_id_pattern%" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions", + "size" + ], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": [ + "id", + "displayName" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + }, + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile0.txt"] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "%space_id_pattern%" + }, + "driveType" : { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["group"], + "properties":{ + "group": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + ] + }, + "size": { + "type": "number", + "enum": [5] + } + } + }, + "size": { + "type": "number", + "enum": [5] + } + } + } + } + } + } + """ + + + Scenario: user lists file shared with them in a group from sharer's project space + Given using spaces DAV path + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "textfile0.txt" + And user "Alice" has created a group "grp1" using the Graph API + And user "Brian" has been added to group "grp1" + And user "Alice" has sent the following share invitation: + | resource | textfile0.txt | + | space | new-space | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem", + "size" + ], + "properties": { + "@UI.Hidden":{ + "type": "boolean", + "enum": [false] + }, + "@client.synchronize":{ + "type": "boolean", + "enum": [true] + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile0.txt"] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType", + "id" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType" : { + "type": "string", + "enum": ["virtual"] + }, + "id" : { + "type": "string", + "pattern": "%space_id_pattern%" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "eTag", + "file", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions" + ], + "properties": { + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { + "type": "string", + "enum": ["text/plain"] + } + } + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["textfile0.txt"] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "%space_id_pattern%" + }, + "driveType" : { + "type": "string", + "enum": ["project"] + } + } + }, + "permissions": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["group"], + "properties":{ + "group": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + ] + }, + "size": { + "type": "number", + "enum": [11] + } + } + }, + "size": { + "type": "number", + "enum": [11] + } + } + } + } + } + } + """ + + + Scenario: user lists folder shared with them in a group from sharer's personal space + Given using spaces DAV path + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created folder "folder" + And user "Alice" has created a group "grp1" using the Graph API + And user "Brian" has been added to group "grp1" + And user "Alice" has sent the following share invitation: + | resource | folder | + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem" + ], + "properties": { + "@UI.Hidden":{ + "type": "boolean", + "enum": [false] + }, + "@client.synchronize":{ + "type": "boolean", + "enum": [true] + }, + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folder"] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType", + "id" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType" : { + "type": "string", + "enum": ["virtual"] + }, + "id" : { + "type": "string", + "pattern": "%space_id_pattern%" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "createdBy", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions" + ], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["id","displayName"], + "properties": { + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + }, + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + } + } + } + } + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folder"] + }, + "parentReference": { + "type": "object", + "required": ["driveId","driveType"], + "properties": { + "driveId": { + "type": "string", + "pattern": "%space_id_pattern%" + }, + "driveType" : { + "type": "string", + "enum": ["personal"] + } + } + }, + "permissions": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["group"], + "properties":{ + "group": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + ] + } + } + } + } + } + } + } + } + """ + + + Scenario: user lists folder shared with them in a group from sharer's project space + Given using spaces DAV path + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has created a group "grp1" using the Graph API + And user "Alice" has created a folder "folder" in space "new-space" + And user "Brian" has been added to group "grp1" + And user "Alice" has sent the following share invitation: + | resource | folder | + | space | new-space | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Viewer | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "remoteItem" + ], + "properties": { + "@UI.Hidden":{ + "type": "boolean", + "enum": [false] + }, + "@client.synchronize":{ + "type": "boolean", + "enum": [true] + }, + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%share_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folder"] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType", + "id" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "driveType" : { + "type": "string", + "enum": ["virtual"] + }, + "id" : { + "type": "string", + "pattern": "%space_id_pattern%" + } + } + }, + "remoteItem": { + "type": "object", + "required": [ + "eTag", + "folder", + "id", + "lastModifiedDateTime", + "name", + "parentReference", + "permissions" + ], + "properties": { + "eTag": { + "type": "string", + "pattern": "%etag_pattern%" + }, + "id": { + "type": "string", + "pattern": "^%file_id_pattern%$" + }, + "name": { + "type": "string", + "enum": ["folder"] + }, + "parentReference": { + "type": "object", + "required": [ + "driveId", + "driveType" + ], + "properties": { + "driveId": { + "type": "string", + "pattern": "%space_id_pattern%" + }, + "driveType" : { + "type": "string", + "enum": ["project"] + } + } + }, + "permissions": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": [ + { + "type": "object", + "required": [ + "grantedToV2", + "id", + "invitation", + "roles" + ], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["group"], + "properties":{ + "group": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["grp1"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "id": { + "type": "string", + "pattern": "^%permissions_id_pattern%$" + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "user":{ + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "type": "string", + "enum": ["Alice Hansen"] + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "roles": { + "type": "array", + "items": [ + { + "type": "string", + "pattern": "^%role_id_pattern%$" + } + ] + } + } + } + ] + } + } + } + } + } + } + } + } + """ From 7af7013b3af02d3e9e302d7868a578d0aa1416b8 Mon Sep 17 00:00:00 2001 From: Sawjan Gurung Date: Thu, 15 Feb 2024 14:02:59 +0545 Subject: [PATCH 108/112] [tests-only] extend search tests for mediatype filter (#8446) * test(api): extend search tests for mediatype filter * test(api): fix php code style * test(api): fix null argument --- .../apiSearch2/mediaTypeSearch.feature | 92 ++++++++++++++----- .../acceptance/features/bootstrap/WebDav.php | 8 +- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature b/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature index eb72306ea..48a8ebfd4 100644 --- a/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature +++ b/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature @@ -70,16 +70,16 @@ Feature: media type search Scenario Outline: search for file inside project space using media type Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API - And user "Alice" has created a space "find data" with the default quota using the Graph API - And user "Alice" has uploaded a file "filesForUpload/lorem.txt" to "/lorem.txt" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/simple.pdf" to "/simple.pdf" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/testavatar.jpg" to "/testavatar.jpg" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/testavatar.png" to "/testavatar.png" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/data.tar.gz" to "/data.tar.gz" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/data.tar" to "/data.tar" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/data.7z" to "/data.7z" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/data.rar" to "/data.rar" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/data.tar.bz2" to "/data.tar.bz2" in space "find data" + And user "Alice" has created a space "project101" with the default quota using the Graph API + And user "Alice" has uploaded a file "filesForUpload/lorem.txt" to "/lorem.txt" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/simple.pdf" to "/simple.pdf" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/testavatar.jpg" to "/testavatar.jpg" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/testavatar.png" to "/testavatar.png" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/data.tar.gz" to "/data.tar.gz" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/data.tar" to "/data.tar" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/data.7z" to "/data.7z" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/data.rar" to "/data.rar" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/data.tar.bz2" to "/data.tar.bz2" in space "project101" When user "Alice" searches for "mediatype:" using the WebDAV API Then the HTTP status code should be "207" And the search result should contain "1" entries @@ -128,19 +128,19 @@ Feature: media type search | *bzip2* | /uploadFolder/data.tar.bz2 | - Scenario Outline: sharee searches for files inside shared space using media type + Scenario Outline: space viewer searches for files using mediatype filter Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API - And user "Alice" has created a space "find data" with the default quota using the Graph API - And user "Alice" has uploaded a file "filesForUpload/lorem.txt" to "/lorem.txt" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/simple.pdf" to "/simple.pdf" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/testavatar.jpg" to "/testavatar.jpg" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/testavatar.png" to "/testavatar.png" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/data.tar.gz" to "/data.tar.gz" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/data.tar" to "/data.tar" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/data.7z" to "/data.7z" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/data.rar" to "/data.rar" in space "find data" - And user "Alice" has uploaded a file "filesForUpload/data.tar.bz2" to "/data.tar.bz2" in space "find data" - And user "Alice" has shared a space "find data" with settings: + And user "Alice" has created a space "project101" with the default quota using the Graph API + And user "Alice" has uploaded a file "filesForUpload/lorem.txt" to "/lorem.txt" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/simple.pdf" to "/simple.pdf" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/testavatar.jpg" to "/testavatar.jpg" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/testavatar.png" to "/testavatar.png" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/data.tar.gz" to "/data.tar.gz" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/data.tar" to "/data.tar" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/data.7z" to "/data.7z" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/data.rar" to "/data.rar" in space "project101" + And user "Alice" has uploaded a file "filesForUpload/data.tar.bz2" to "/data.tar.bz2" in space "project101" + And user "Alice" has shared a space "project101" with settings: | shareWith | Brian | | role | viewer | When user "Brian" searches for "mediatype:" using the WebDAV API @@ -159,3 +159,51 @@ Feature: media type search | *7z* | /data.7z | | *rar* | /data.rar | | *bzip2* | /data.tar.bz2 | + + + Scenario: search files with different mediatype filter + Given user "Alice" has created folder "testFolder" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "lorem.txt" + And user "Alice" has uploaded file "filesForUpload/simple.odt" to "simple.odt" + And user "Alice" has uploaded file "filesForUpload/simple.pdf" to "simple.pdf" + And user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "testavatar.jpg" + And user "Alice" has uploaded file "filesForUpload/testavatar.png" to "testavatar.png" + And user "Alice" has uploaded file "filesForUpload/example.gif" to "example.gif" + And user "Alice" has uploaded file "filesForUpload/data.tar.gz" to "data.tar.gz" + And user "Alice" has uploaded file "filesForUpload/data.tar" to "data.tar" + And user "Alice" has uploaded file "filesForUpload/data.7z" to "data.7z" + And user "Alice" has uploaded file "filesForUpload/data.rar" to "data.rar" + And user "Alice" has uploaded file "filesForUpload/data.tar.bz2" to "data.tar.bz2" + When user "Alice" searches for "mediatype:folder" using the WebDAV API + Then the HTTP status code should be "207" + And the search result should contain "2" entries + And the search result of user "Alice" should contain these entries: + | %spaceid% | + | testFolder | + When user "Alice" searches for "mediatype:document" using the WebDAV API + Then the HTTP status code should be "207" + And the search result should contain "2" entries + And the search result of user "Alice" should contain these entries: + | lorem.txt | + | simple.odt | + When user "Alice" searches for "mediatype:pdf" using the WebDAV API + Then the HTTP status code should be "207" + And the search result should contain "1" entries + And the search result of user "Alice" should contain these entries: + | simple.pdf | + When user "Alice" searches for "mediatype:image" using the WebDAV API + Then the HTTP status code should be "207" + And the search result should contain "3" entries + And the search result of user "Alice" should contain these entries: + | testavatar.jpg | + | testavatar.png | + | example.gif | + When user "Alice" searches for "mediatype:archive" using the WebDAV API + Then the HTTP status code should be "207" + And the search result should contain "5" entries + And the search result of user "Alice" should contain these entries: + | data.tar.gz | + | data.tar | + | data.7z | + | data.rar | + | data.tar.bz2 | diff --git a/tests/acceptance/features/bootstrap/WebDav.php b/tests/acceptance/features/bootstrap/WebDav.php index 65c07b7e7..03e15d2d3 100644 --- a/tests/acceptance/features/bootstrap/WebDav.php +++ b/tests/acceptance/features/bootstrap/WebDav.php @@ -5280,6 +5280,7 @@ trait WebDav { $should = ($shouldOrNot !== "not"); foreach ($elementRows as $expectedFile) { $resource = $expectedFile[0]; + $resource = $this->substituteInLineCodes($resource, $user); if ($resource === '') { continue; } @@ -5769,7 +5770,12 @@ trait WebDav { $href = (string)$item->xpath("d:href")[0]; $shareRootXml = $item->xpath("d:propstat//oc:shareroot"); $href = \str_replace($spacesBaseUrl, "", $href); - $resourcePath = \substr($href, \strpos($href, '/') + 1); + $resourcePath = $href; + // do not try to parse the resouce path + // if the item to search is space itself + if (!GraphHelper::isSpaceId($entryNameToSearch ?? '')) { + $resourcePath = \substr($href, \strpos($href, '/') + 1); + } if (\count($shareRootXml)) { $shareroot = \trim((string)$shareRootXml[0], "/"); $resourcePath = $shareroot . "/" . $resourcePath; From 13da4b20978450123293507767767f761a72426a Mon Sep 17 00:00:00 2001 From: Viktor Scharf Date: Thu, 15 Feb 2024 10:44:22 +0100 Subject: [PATCH 109/112] change antivirus env name --- .../general-info/env-var-deltas/4.0.0-5.0.0-added.adoc | 6 +++--- .../general-info/env-var-deltas/4.0.0-5.0.0-added.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc index 5fe2337a9..14b6b444f 100644 --- a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.adoc @@ -28,8 +28,8 @@ | `OCIS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services.| | -| `ANTIVIRUS_ICAP_TIMEOUT` -| Scan timeout for the ICAP client. Defaults to '5m' (5 minutes). See the Environment Variable Types description for more details.| +| `ANTIVIRUS_ICAP_SCAN_TIMEOUT` +| Scan timeout for the ICAP client. Defaults to '5m' (5 minutes). See the Environment Variable Types description for more details.| 5m0s | xref:{s-path}/audit.adoc[audit] | `OCIS_EVENTS_AUTH_USERNAME` @@ -216,7 +216,7 @@ | Defines the default permissions a link is being created with. Possible values are 0 (= internal link, for instance members only) and 1 (= public link with viewer permissions). Defaults to 1.| | | `FRONTEND_AUTO_ACCEPT_SHARES` -| Defines if shares should be auto accepted by default. Users can change this setting individually in their profile.| +| Defines if shares should be auto accepted by default. Users can change this setting individually in their profile.| true | | `OCIS_CACHE_DISABLE_PERSISTENCE` + `FRONTEND_OCS_STAT_CACHE_DISABLE_PERSISTENCE` diff --git a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md index a58a4ef27..9303ac2a6 100644 --- a/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md +++ b/docs/services/general-info/env-var-deltas/4.0.0-5.0.0-added.md @@ -8,7 +8,7 @@ | | `MICRO_REGISTRY_AUTH_PASSWORD` | Optional when using nats to authenticate with the nats cluster. | | | services/antivirus/pkg/config/config.go | `OCIS_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | | | `OCIS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | -| | `ANTIVIRUS_ICAP_TIMEOUT` | Scan timeout for the ICAP client. Defaults to '5m' (5 minutes). See the Environment Variable Types description for more details. | | +| | `ANTIVIRUS_ICAP_SCAN_TIMEOUT` | Scan timeout for the ICAP client. Defaults to '5m' (5 minutes). See the Environment Variable Types description for more details. | 5m0s | | services/audit/pkg/config/config.go | `OCIS_EVENTS_AUTH_USERNAME` | The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | | | `OCIS_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | | services/auth-service/pkg/config/config.go | `OCIS_TRACING_ENABLED;AUTH_SERVICE_TRACING_ENABLED` | Activates tracing. | | @@ -56,7 +56,7 @@ | | `OCIS_EVENTS_AUTH_PASSWORD;EVENTHISTORY_EVENTS_AUTH_PASSWORD` | The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services. | | | services/frontend/pkg/config/config.go | `OCIS_ENABLE_RESHARING` | Changing this value is NOT supported. Enables the support for resharing in the clients. | | | | `FRONTEND_DEFAULT_LINK_PERMISSIONS` | Defines the default permissions a link is being created with. Possible values are 0 (= internal link, for instance members only) and 1 (= public link with viewer permissions). Defaults to 1. | | -| | `FRONTEND_AUTO_ACCEPT_SHARES` | Defines if shares should be auto accepted by default. Users can change this setting individually in their profile. | | +| | `FRONTEND_AUTO_ACCEPT_SHARES` | Defines if shares should be auto accepted by default. Users can change this setting individually in their profile. | true | | | `OCIS_CACHE_DISABLE_PERSISTENCE;FRONTEND_OCS_STAT_CACHE_DISABLE_PERSISTENCE` | Disable persistence of the cache. Only applies when using the 'nats-js-kv' store type. Defaults to false. | false | | | `FRONTEND_OCS_LIST_OCM_SHARES` | Include OCM shares when listing shares. See the OCM service documentation for more details. | | | | `OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_SHARE_MUST_HAVE_PASSWORD` | Set this to true if you want to enforce passwords on all public shares. | | From a1921ecada2c8d035b773456dac822143f20ae91 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Thu, 15 Feb 2024 14:55:53 +0100 Subject: [PATCH 110/112] docs: remove duplicate changelog item --- changelog/unreleased/fix-signed-url-expiry.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 changelog/unreleased/fix-signed-url-expiry.md diff --git a/changelog/unreleased/fix-signed-url-expiry.md b/changelog/unreleased/fix-signed-url-expiry.md deleted file mode 100644 index 9b0cdf3da..000000000 --- a/changelog/unreleased/fix-signed-url-expiry.md +++ /dev/null @@ -1,5 +0,0 @@ -Bugfix: signed url verification - -Signed urls now expire properly - -https://github.com/owncloud/ocis/pull/8385 From 9cbeb14f1c3c95de047fc4dfddd4bb59fd220e82 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Thu, 15 Feb 2024 16:40:26 +0100 Subject: [PATCH 111/112] chore: revert changes that are not needed from master --- .drone.star | 53 +++++++++++++++++-------------------- ocis-pkg/version/version.go | 2 +- sonar-project.properties | 2 +- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/.drone.star b/.drone.star index 3f86efd1f..9908c5a02 100644 --- a/.drone.star +++ b/.drone.star @@ -302,7 +302,7 @@ def cachePipeline(name, steps): "steps": steps, "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/tags/**", "refs/pull/**", ], @@ -362,7 +362,6 @@ def getGoBinForTesting(ctx): cacheGoBin(), "trigger": { "ref": [ - "refs/heads/master", "refs/heads/stable-*", "refs/pull/**", ], @@ -501,7 +500,7 @@ def testOcis(ctx): "steps": steps, "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], }, @@ -557,7 +556,7 @@ def buildOcisBinaryForTesting(ctx): rebuildBuildArtifactCache(ctx, "ocis-binary-amd64", "ocis/bin"), "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], }, @@ -644,7 +643,7 @@ def uploadScanResults(ctx): ], "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], "status": [ @@ -758,7 +757,7 @@ def codestyle(ctx): "depends_on": [], "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", "refs/tags/**", ], @@ -812,7 +811,7 @@ def localApiTestPipeline(ctx): "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], }, @@ -872,7 +871,7 @@ def cs3ApiTests(ctx, storage, accounts_hash_difficulty = 4): "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], }, @@ -979,7 +978,7 @@ def wopiValidatorTests(ctx, storage, accounts_hash_difficulty = 4): "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], }, @@ -1028,7 +1027,7 @@ def coreApiTests(ctx, part_number = 1, number_of_parts = 1, storage = "ocis", ac "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], }, @@ -1152,7 +1151,7 @@ def uiTestPipeline(ctx, filterTags, runPart = 1, numberOfParts = 1, storage = "o "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)] + buildWebCache(ctx)), "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], }, @@ -1176,7 +1175,7 @@ def e2eTests(ctx): e2e_trigger = { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/tags/**", "refs/pull/**", ], @@ -1385,7 +1384,7 @@ def dockerRelease(ctx, arch): "depends_on": depends_on, "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/tags/v*", "refs/pull/**", ], @@ -1462,7 +1461,6 @@ def binaryRelease(ctx, name): ], "when": { "ref": [ - "refs/heads/master", "refs/tags/v*", ], }, @@ -1473,7 +1471,6 @@ def binaryRelease(ctx, name): "settings": settings, "when": { "ref": [ - "refs/heads/master", "refs/tags/v*", ], }, @@ -1516,7 +1513,7 @@ def binaryRelease(ctx, name): "depends_on": depends_on, "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/tags/v*", "refs/pull/**", ], @@ -1608,7 +1605,6 @@ def licenseCheck(ctx): "settings": settings, "when": { "ref": [ - "refs/heads/master", "refs/tags/v*", ], }, @@ -1618,7 +1614,7 @@ def licenseCheck(ctx): "image": OC_CI_GOLANG, "environment": DRONE_HTTP_PROXY_ENV, "commands": [ - "make changelog CHANGELOG_VERSION=%s" % ctx.build.ref.replace("refs/tags/v", "").split("-")[0], + "make changelog CHANGELOG_VERSION=%s" % ctx.build.ref.replace("refs/tags/v", ""), ], "when": { "ref": [ @@ -1650,7 +1646,6 @@ def licenseCheck(ctx): ], "trigger": { "ref": [ - "refs/heads/master", "refs/tags/v*", "refs/pull/**", ], @@ -1686,7 +1681,6 @@ def releaseDockerManifest(): ], "trigger": { "ref": [ - "refs/heads/master", "refs/tags/v*", ], }, @@ -1733,7 +1727,7 @@ def changelog(): "push", ], "message": "Automated changelog update [skip ci]", - "branch": "master", + "branch": "stable-5.0", "author_email": "devops@owncloud.com", "author_name": "ownClouders", "netrc_machine": "github.com", @@ -1755,7 +1749,7 @@ def changelog(): ], "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], }, @@ -1789,7 +1783,6 @@ def releaseDockerReadme(ctx): ], "trigger": { "ref": [ - "refs/heads/master", "refs/tags/v*", ], }, @@ -1839,7 +1832,7 @@ def docs(): }, "pages_directory": "docs/hugo/content/", "copy_contents": "true", - "target_branch": "docs", + "target_branch": "docs-stable-5.0", "delete": "true", }, "when": { @@ -1861,7 +1854,7 @@ def docs(): ], "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], }, @@ -1927,7 +1920,7 @@ def notify(): "depends_on": [], "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/heads/release*", "refs/tags/**", ], @@ -2232,7 +2225,6 @@ def deploy(ctx, config, rebuild): ], "trigger": { "ref": [ - "refs/heads/master", "refs/tags/v*", ], }, @@ -2337,7 +2329,7 @@ def genericCachePurge(flush_path): ], "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], "status": [ @@ -2589,7 +2581,7 @@ def litmus(ctx, storage): "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), "trigger": { "ref": [ - "refs/heads/master", + "refs/heads/stable-*", "refs/pull/**", ], }, @@ -2895,5 +2887,8 @@ def k6LoadTests(ctx): "event": [ "cron", ], + "ref": [ + "refs/heads/master", + ], }, }] diff --git a/ocis-pkg/version/version.go b/ocis-pkg/version/version.go index 86a21cac1..fe27cc1f6 100644 --- a/ocis-pkg/version/version.go +++ b/ocis-pkg/version/version.go @@ -16,7 +16,7 @@ var ( // LatestTag is the latest released version plus the dev meta version. // Will be overwritten by the release pipeline // Needs a manual change for every tagged release - LatestTag = "5.1.0-prealpha+dev" + LatestTag = "5.0.0-rc.4+dev" // Date indicates the build date. // This has been removed, it looks like you can only replace static strings with recent go versions diff --git a/sonar-project.properties b/sonar-project.properties index c4ada13e4..e9383ea45 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ sonar.projectKey=owncloud_ocis sonar.organization=owncloud-1 sonar.projectName=ocis -sonar.projectVersion=5.1.0-prealpha +sonar.projectVersion=5.0.0-rc.4 sonar.host.url=https://sonarcloud.io # ===================================================== From 80c92743eb2bbfd43a43dafff8c0861b7464ef38 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Thu, 15 Feb 2024 17:21:32 +0100 Subject: [PATCH 112/112] chore: bump web to v8.0.0-rc.4 --- .drone.env | 2 +- changelog/unreleased/update-web-8.0.0.md | 159 +++++++++++++++++++++++ services/web/Makefile | 2 +- 3 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 changelog/unreleased/update-web-8.0.0.md diff --git a/.drone.env b/.drone.env index 1a92c7738..a3f7c8506 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for UI tests -WEB_COMMITID=304fa011f59131657b5fc9698d0627b2b6d8c694 +WEB_COMMITID=6fca4f4f12aa58d21c73d299b47bda2a9407eb4e WEB_BRANCH=stable-8.0 diff --git a/changelog/unreleased/update-web-8.0.0.md b/changelog/unreleased/update-web-8.0.0.md new file mode 100644 index 000000000..f30738b62 --- /dev/null +++ b/changelog/unreleased/update-web-8.0.0.md @@ -0,0 +1,159 @@ +Enhancement: Update web to v8.0.0-rc.4 + +Tags: web + +We updated ownCloud Web to v8.0.0-rc.4. Please refer to the changelog (linked) for details on the web release. + +## Summary +* Bugfix [owncloud/web#10489](https://github.com/owncloud/web/pull/10489): Wrong share permissions when resharing off + +We updated ownCloud Web to v8.0.0-rc.3. Please refer to the changelog (linked) for details on the web release. + +## Summary +* Bugfix [owncloud/web#10318](https://github.com/owncloud/web/pull/10318): Scrollable account page +* Bugfix [owncloud/web#10321](https://github.com/owncloud/web/pull/10321): Private link error messages +* Bugfix [owncloud/web#10347](https://github.com/owncloud/web/pull/10347): Readonly user attributes have no effect on group memberships +* Bugfix [owncloud/web#10424](https://github.com/owncloud/web/pull/10424): Restore space +* Enhancement [owncloud/web#10356](https://github.com/owncloud/web/pull/10356): Preview app add reset button for images + +We updated ownCloud Web to v8.0.0-rc.2. Please refer to the changelog (linked) for details on the web release. + +## Summary +* Bugfix [owncloud/web#10176](https://github.com/owncloud/web/pull/10176): Turned off file extensions not always respected +* Bugfix [owncloud/web#10232](https://github.com/owncloud/web/pull/10232): Skip searchbar preview fetch on reload + +We updated ownCloud Web to v8.0.0-rc.1. Please refer to the changelog (linked) for details on the web release. + +## Summary +* Enhancement [owncloud/web#10224](https://github.com/owncloud/web/issues/10224): Harmonize AppSwitcher icon colors +* Bugfix [owncloud/web#10230](https://github.com/owncloud/web/issues/10230): Configurable concurrent requests +* Bugfix [owncloud/web#10158](https://github.com/owncloud/web/issues/10158): GDPR export polling +* Bugfix [owncloud/web#10220](https://github.com/owncloud/web/issues/10220): Loading indicator during conflict dialog +* Bugfix [owncloud/web#10156](https://github.com/owncloud/web/issues/10156): Uploading the same files parallel +* Bugfix [owncloud/web#10179](https://github.com/owncloud/web/issues/10179): Space navigate to trash missing +* Bugfix [owncloud/web#10118](https://github.com/owncloud/web/issues/10118): Tilesview has whitespace +* Bugfix [owncloud/web#10182](https://github.com/owncloud/web/issues/10182): Make versions panel readonly in viewers and editors + +We updated ownCloud Web to v8.0.0-beta.2. Please refer to the changelog (linked) for details on the web release. + +## Summary +* Bugfix [owncloud/web#10010](https://github.com/owncloud/web/issues/10010): Displaying full video in their dimensions +* Bugfix [owncloud/web#10149](https://github.com/owncloud/web/pull/10149): Spaces files list previews cropped +* Bugfix [owncloud/web#10149](https://github.com/owncloud/web/pull/10149): Spaces overview tile previews zoomed +* Bugfix [owncloud/web#10154](https://github.com/owncloud/web/pull/10154): Resolving links without drive alias + +We updated ownCloud Web to v8.0.0-beta.1. Please refer to the changelog (linked) for details on the web release. + +## Summary +* Change [owncloud/web#9698](https://github.com/owncloud/web/pull/9698): Theme handling +* Enhancement [owncloud/web#10111](https://github.com/owncloud/web/pull/10111): Registering right sidebar panels as extension +* Enhancement [owncloud/web#10111](https://github.com/owncloud/web/pull/10111): File sidebar in viewer and editor apps + +We updated ownCloud Web to v8.0.0-alpha.13. Please refer to the changelog (linked) for details on the web release. + +## Summary +* Enhancement [owncloud/web#10104](https://github.com/owncloud/web/pull/10104): Create link modal + +We updated ownCloud Web to v8.0.0-alpha.12. Please refer to the changelog (linked) for details on the web release. + +## Summary +* Bugfix [owncloud/web#9257](https://github.com/owncloud/web/issues/9257): Filter out shares without display name +* Bugfix [owncloud/web#9483](https://github.com/owncloud/web/issues/9483): PDF loading Safari +* Bugfix [owncloud/web#9513](https://github.com/owncloud/web/pull/9513): Set or remove expiration date on group share not possible +* Bugfix [owncloud/web#9529](https://github.com/owncloud/web/pull/9529): Shared with action menu label alignment +* Bugfix [owncloud/web#9587](https://github.com/owncloud/web/pull/9587): Internal public link resolving +* Bugfix [owncloud/web#9593](https://github.com/owncloud/web/issues/9593): Audio- & video-loading on Shared with me page +* Bugfix [owncloud/web#9649](https://github.com/owncloud/web/pull/9649): Add project space filter +* Bugfix [owncloud/web#9663](https://github.com/owncloud/web/pull/9663): Respect the open-in-new-tab-config for external apps +* Bugfix [owncloud/web#9670](https://github.com/owncloud/web/pull/9670): Tiles view accessibility +* Bugfix [owncloud/web#9694](https://github.com/owncloud/web/issues/9694): Special characters in username +* Bugfix [owncloud/web#9788](https://github.com/owncloud/web/issues/9788): Create .space folder if it does not exist +* Bugfix [owncloud/web#9799](https://github.com/owncloud/web/issues/9799): Link resolving into default app +* Bugfix [owncloud/web#9832](https://github.com/owncloud/web/pull/9832): Copy quicklinks for webkit navigator +* Bugfix [owncloud/web#9843](https://github.com/owncloud/web/pull/9843): Fix display path on resources +* Bugfix [owncloud/web#9844](https://github.com/owncloud/web/pull/9844): Upload space image +* Bugfix [owncloud/web#9861](https://github.com/owncloud/web/pull/9861): Duplicated file search request +* Bugfix [owncloud/web#9873](https://github.com/owncloud/web/pull/9873): Tags are no longer editable for a locked file +* Bugfix [owncloud/web#9881](https://github.com/owncloud/web/pull/9881): Prevent rendering of old/wrong set of resources in search list +* Bugfix [owncloud/web#9915](https://github.com/owncloud/web/pull/9915): Keep both folders conflict in same-named folders +* Bugfix [owncloud/web#9931](https://github.com/owncloud/web/pull/9931): Enabling "invite people" for password-protected folder/file +* Bugfix [owncloud/web#10031](https://github.com/owncloud/web/issues/10031): Icon extension mapping +* Bugfix [owncloud/web#10065](https://github.com/owncloud/web/pull/10065): Logout page after token expiry +* Bugfix [owncloud/web#10083](https://github.com/owncloud/web/pull/10083): Disable expiration date for alias link (internal) +* Bugfix [owncloud/web#10092](https://github.com/owncloud/web/pull/10092): Allow empty search query in "in-here" search +* Bugfix [owncloud/web#10096](https://github.com/owncloud/web/pull/10096): Remove password buttons on input if disabled +* Change [owncloud/web#7338](https://github.com/owncloud/web/issues/7338): Remove deprecated code +* Enhancement [owncloud/web#7317](https://github.com/owncloud/ocis/pull/7317): Make login url configurable +* Enhancement [owncloud/web#7497](https://github.com/owncloud/ocis/issues/7497): Permission checks for shares and favorites +* Enhancement [owncloud/web#7600](https://github.com/owncloud/web/issues/7600): Scroll to newly created folder +* Enhancement [owncloud/web#9302](https://github.com/owncloud/web/issues/9302): Application unification +* Enhancement [owncloud/web#9423](https://github.com/owncloud/web/pull/9423): Show local loading spinner in sharing button +* Enhancement [owncloud/web#9441](https://github.com/owncloud/web/pull/9441): File versions tooltip with absolute date +* Enhancement [owncloud/web#9441](https://github.com/owncloud/web/pull/9441): Disabling extensions +* Enhancement [owncloud/web#9451](https://github.com/owncloud/web/pull/9451): Add SSE to get notifications instantly +* Enhancement [owncloud/web#9525](https://github.com/owncloud/web/pull/9525): Tags form improved +* Enhancement [owncloud/web#9527](https://github.com/owncloud/web/pull/9527): Don't display confirmation dialog on file deletion +* Enhancement [owncloud/web#9531](https://github.com/owncloud/web/issues/9531): Personal shares can be shown and hidden +* Enhancement [owncloud/web#9552](https://github.com/owncloud/web/pull/9552): Upload preparation time +* Enhancement [owncloud/web#9561](https://github.com/owncloud/web/pull/9561): Indicate processing state +* Enhancement [owncloud/web#9566](https://github.com/owncloud/web/pull/9566): Display locking information +* Enhancement [owncloud/web#9584](https://github.com/owncloud/web/pull/9584): Moving share's "set expiration date" function +* Enhancement [owncloud/web#9625](https://github.com/owncloud/web/pull/9625): Add keyboard navigation to spaces overview +* Enhancement [owncloud/web#9627](https://github.com/owncloud/web/pull/9627): Add batch actions to spaces +* Enhancement [owncloud/web#9653](https://github.com/owncloud/web/pull/9653): Keyword Query Language (KQL) search syntax +* Enhancement [owncloud/web#9671](https://github.com/owncloud/web/pull/9671): OcModal set buttons to same width +* Enhancement [owncloud/web#9682](https://github.com/owncloud/web/pull/9682): Add password policy compatibility +* Enhancement [owncloud/web#9691](https://github.com/owncloud/web/pull/9691): Password generator for public links +* Enhancement [owncloud/web#9696](https://github.com/owncloud/web/pull/9696): Added app banner for mobile devices +* Enhancement [owncloud/web#9706](https://github.com/owncloud/web/pull/9706): Unify sharing expiration date menu items +* Enhancement [owncloud/web#9727](https://github.com/owncloud/web/pull/9727): Show error if password is on a banned password list +* Enhancement [owncloud/web#9771](https://github.com/owncloud/web/pull/9771): Handle postprocessing state via Server Sent Events +* Enhancement [owncloud/web#9806](https://github.com/owncloud/web/pull/9806): Preview image presentation +* Enhancement [owncloud/web#9809](https://github.com/owncloud/web/pull/9809): Add editors to the application menu +* Enhancement [owncloud/web#9814](https://github.com/owncloud/web/pull/9814): Registering nav items as extension +* Enhancement [owncloud/web#9815](https://github.com/owncloud/web/pull/9815): Add new portal into runtime to include footer +* Enhancement [owncloud/web#9818](https://github.com/owncloud/web/pull/9818): Add `mode` config option +* Enhancement [owncloud/web#9831](https://github.com/owncloud/web/pull/9831): Last modified filter chips +* Enhancement [owncloud/web#9841](https://github.com/owncloud/web/pull/9841): Add embed mode actions +* Enhancement [owncloud/web#9847](https://github.com/owncloud/web/issues/9847): Provide vendor neutral file icons +* Enhancement [owncloud/web#9853](https://github.com/owncloud/web/pull/9853): Show only create folder button in embed mode +* Enhancement [owncloud/web#9854](https://github.com/owncloud/web/pull/9854): Search query term linking +* Enhancement [owncloud/web#9857](https://github.com/owncloud/web/pull/9857): Add permission to delete link passwords when password is enforced +* Enhancement [owncloud/web#9858](https://github.com/owncloud/web/pull/9858): Remove settings icon from searchbar +* Enhancement [owncloud/web#9863](https://github.com/owncloud/web/pull/9863): Location picker in embed mode +* Enhancement [owncloud/web#9864](https://github.com/owncloud/web/pull/9864): Search tags filter chips style aligned +* Enhancement [owncloud/web#9884](https://github.com/owncloud/web/pull/9884): Enable dark theme on importer +* Enhancement [owncloud/web#9890](https://github.com/owncloud/web/pull/9890): Create shortcuts +* Enhancement [owncloud/web#9905](https://github.com/owncloud/web/pull/9905): Manage tags in details panel +* Enhancement [owncloud/web#9906](https://github.com/owncloud/web/pull/9906): Reorganize "New" menu +* Enhancement [owncloud/web#9912](https://github.com/owncloud/web/pull/9912): Add media type filter chip +* Enhancement [owncloud/web#9940](https://github.com/owncloud/web/pull/9940): Display error message for upload to locked folder +* Enhancement [owncloud/web#9966](https://github.com/owncloud/web/issues/9966): Support more audio formats with correct icon +* Enhancement [owncloud/web#10007](https://github.com/owncloud/web/issues/10007): Additional languages +* Enhancement [owncloud/web#10013](https://github.com/owncloud/web/issues/10013): Shared by filter +* Enhancement [owncloud/web#10014](https://github.com/owncloud/web/issues/10014): Share search filter +* Enhancement [owncloud/web#10024](https://github.com/owncloud/web/pull/10024): Duplicate space +* Enhancement [owncloud/web#10037](https://github.com/owncloud/web/pull/10037): Default link permission +* Enhancement [owncloud/web#10047](https://github.com/owncloud/web/pull/10047): Add explaining contextual helper to spaces overview +* Enhancement [owncloud/web#10057](https://github.com/owncloud/web/pull/10057): Folder tree creation during upload +* Enhancement [owncloud/web#10062](https://github.com/owncloud/web/pull/10062): Show webdav information in details view +* Enhancement [owncloud/web#10072](https://github.com/owncloud/web/issues/10072): Add authentication delegation in the Embed mode +* Enhancement [owncloud/web#10099](https://github.com/owncloud/web/pull/10099): Support mandatory filter while listing users +* Enhancement [owncloud/web#10102](https://github.com/owncloud/web/pull/10102): Registering quick actions as extension + +https://github.com/owncloud/ocis/pull/8342 +https://github.com/owncloud/web/releases/tag/v8.0.0-rc.3 +https://github.com/owncloud/ocis/pull/8154 +https://github.com/owncloud/web/releases/tag/v8.0.0-rc.2 +https://github.com/owncloud/ocis/pull/8154 +https://github.com/owncloud/web/releases/tag/v8.0.0-rc.2 +https://github.com/owncloud/ocis/pull/8055 +https://github.com/owncloud/web/releases/tag/v8.0.0-rc.1 +https://github.com/owncloud/ocis/pull/7930 +https://github.com/owncloud/web/releases/tag/v8.0.0-beta.1 +https://github.com/owncloud/ocis/pull/7952 +https://github.com/owncloud/web/releases/tag/v8.0.0-beta.2 +https://github.com/owncloud/ocis/pull/7918 +https://github.com/owncloud/web/releases/tag/v8.0.0-alpha.13 +https://github.com/owncloud/ocis/pull/7883 +https://github.com/owncloud/web/releases/tag/v8.0.0-alpha.12 diff --git a/services/web/Makefile b/services/web/Makefile index d25b19aec..b5da39a7b 100644 --- a/services/web/Makefile +++ b/services/web/Makefile @@ -1,6 +1,6 @@ SHELL := bash NAME := web -WEB_ASSETS_VERSION = v8.0.0-rc.3 +WEB_ASSETS_VERSION = v8.0.0-rc.4 include ../../.make/recursion.mk