Repository: owncloud/ocis
Pull request #9326 (2024-06-05 14:10:38)

graph: Small improvments for filtering users by role
This addresses some low hanging fruits for improving the GetUsers call when filtering users by role-assignment (and expanding the assignments).

The first commit avoids a lot of extra queries to the settings service when `$expand=appRoleAssignment` is used together with filtering.

The second commit switch the filtering code to use the new ListRoleAssignmentsFiltered request in the settings service. Reducing the number of queries to the settings service to a single request. (Orignally it was one per existing user + one per user that matched the filter).

On my system with 1000 users (external LDAP) assigned to the `User` role and the metadata service using and NFS volume the query time for: `$filter=(appRoleAssignments/any(m:m/appRoleId eq 'd7beeea8-8ff4-406b-8fb6-ab2dd81e6b11'))&$orderby=displayName&$expand=appRoleAssignments` (the query used by web). I get this:

Cold Caches:
master: ~18s
this pr: ~14s

Hot Caches (mainly the settings service's cache and the kernel's buffercache):
master 0.8s
this pr: 0.4s

So the overall improvement isn't all that big yet (mainly because the settings service itself still needs to scan all assignments in the metadata backend in order to apply the filter). Still, there is some improvement.

Related Issue: #8938 



Patch:
diff --git a/.make/check-env-var-annotations.sh b/.make/check-env-var-annotations.sh
index e133f1610ca..b955b317aa2 100755
--- a/.make/check-env-var-annotations.sh
+++ b/.make/check-env-var-annotations.sh
@@ -5,6 +5,7 @@
 #
 # valid examples:
 #
+# introductionVersion:"%%NEXT%%"
 # introductionVersion:"pre5.0"
 # introductionVersion:"5.0"
 # introductionVersion:"4.9.3-rc5"
@@ -24,7 +25,7 @@ ERROR=0
 
 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 -n "env:" -- '*.go' |grep -v -P "introductionVersion:\"($SEMVER_REGEX|(pre5\.0))\""|grep -v "_test.go"|grep -v "vendor/")
+QUERY_INTRO=$(git grep -n "env:" -- '*.go' |grep -v -P "introductionVersion:\"($SEMVER_REGEX|(pre5\.0)|(%%NEXT%%))\""|grep -v "_test.go"|grep -v "vendor/")
 RESULTS_INTRO=$(echo "${QUERY_INTRO}"|wc -l)
 if [ "${QUERY_INTRO}" != "" ] && [ "${RESULTS_INTRO}" -gt 0 ]; then
   echo "==============================================================================================="
diff --git a/docs/services/general-info/add-translations.md b/docs/services/general-info/add-translations.md
index e7baa56d5b9..df2e9075a3e 100644
--- a/docs/services/general-info/add-translations.md
+++ b/docs/services/general-info/add-translations.md
@@ -20,11 +20,11 @@ Translations are automatically synced on a daily basis in the night.
 
 ## Implementing ocis Translations
 
-The implementation example is a guide and shall show how to do it. You can derive at any time according your needs.
+The implementation example is a guide and shall show how to do it. You can derive at any time according to your needs.
 
 Note that paths are examples and can be adapted based on requirements.\
 Replace `<service-name>` with the name of the respective service.\
-Translations have a `context` and a `translatable string`. The context is shown on Transifex but not translated and helps translators to get a context for the string to be translated. 
+Translations have a `context` and a `translatable string`. The context is shown on Transifex but not translated and helps translators to get a context for the string to be translated.
 
 * Add the `OCIS_DEFAULT_LANGUAGE` envvar in `services/<service-name>/pkg/config/config.go`.\
   For details see the userlog or notifications service code.
@@ -50,7 +50,7 @@ Translations have a `context` and a `translatable string`. The context is shown
   var yourString = l10n.Template("Translation String")
   ```
 
-* In the `Makefile` in the **ocis root**, add in the following section the service you want to synchroize translations with Transifex:
+* In the `Makefile` in the **ocis root**, add in the following section the service you want to synchronize translations with Transifex:
   ```
   # add a service here when it uses transifex
   L10N_MODULES := \
@@ -61,7 +61,7 @@ Translations have a `context` and a `translatable string`. The context is shown
   ```
 
 * In the `Makefile` of the **`<service-name>`** add:\
-  _At the beginnig:_
+  _At the beginning:_
   ```
   # Where to write the files generated by this makefile.
   OUTPUT_DIR = ./pkg/service/<...>/l10n
diff --git a/docs/services/general-info/envvar-scopes.md b/docs/services/general-info/envvar-scopes.md
index 4708510664a..ffe7f1acb00 100644
--- a/docs/services/general-info/envvar-scopes.md
+++ b/docs/services/general-info/envvar-scopes.md
@@ -8,7 +8,7 @@ geekdocFilePath: envvar-scopes.md
 geekdocCollapseSection: true
 ---
 
-The scope of an environment variable can be derived from its name. Therefore it is important to follow the correct naming scheme to enable easy and proper identification. This is important when either:
+The scope of an environment variable can be derived from its name. Therefore, it is important to follow the correct naming scheme to enable easy and proper identification. This is important when either:
 
 -   a new local envvar is introduced.
 -   a new global envvar is added to an existing local envvar.
@@ -24,7 +24,7 @@ The scope of an environment variable can be derived from its name. Therefore it
 
 ### Local Envvars
 
-A local envvar always starts with the the service name like `POSTPROCESSING_LOG_FILE`.
+A local envvar always starts with the service name like `POSTPROCESSING_LOG_FILE`.
 
 ### Global Envvars
 
@@ -34,6 +34,10 @@ Note that this envvar is the global representation of the local example from abo
 
 To get a list of global envvars used in all services, see the [Global Environment Variables](https://doc.owncloud.com/ocis/next/deployment/services/env-vars-special-scope.html#global-environment-variables) table in the ocis admin documentation.
 
+### Lifecycle
+
+In the struct tag values of our config data types, we are using three key/value pairs to document the lifecycle of a config variable: `introductionVersion`, `deprecationVersion` and `removalVersion`. During the development cycle, a new value should set to `%%NEXT%%` as long as no release is scheduled. During the release process, the placeholder will be replaced with the actual version number. Our docs helper scripts will then automatically generate the correct documentation based on the version number.
+
 ## Deprecations
 
 All environment variable types that are used in a service follow the same [deprecation rules]({{< ref "ocis/development/deprecating-variables/_index.md" >}}) independent of their scope.
diff --git a/docs/services/general-info/new-service-checklist.md b/docs/services/general-info/new-service-checklist.md
index 5236d3b4056..80482c4d477 100644
--- a/docs/services/general-info/new-service-checklist.md
+++ b/docs/services/general-info/new-service-checklist.md
@@ -8,7 +8,7 @@ geekdocFilePath: new-service-checklist.md
 geekdocCollapseSection: true
 ---
 
-When a new service gets introduced, this checklist is a good startingpoint for things that need to be completed before the service gets published (merged). This list is without claim of completeness or correct sort order.
+When a new service gets introduced, this checklist is a good starting point for things that need to be completed before the service gets published (merged). This list is without claim of completeness or correct sort order.
 
 ## New Service Checklist
 
@@ -20,9 +20,9 @@ Use this checklist with copy/paste in your PR - right from the beginning. It ren
 - [ ] For images and example files used in README.md:
   - Create a folder named `md-sources` on the same level where README.md is located. Put all the images and example files referenced by README.md into this folder.
   - Use absolute references like `https://raw.githubusercontent.com/owncloud/ocis/master/services/<service-name>/md-sources/file` to make the content accessible for both README.md and owncloud.dev
-    bad `<img src="https://github.com/owncloud/ocis/blob/master/services/graph/images/mermaid-graph.svg" width="500" />`  
+    bad `<img src="https://github.com/owncloud/ocis/blob/master/services/graph/images/mermaid-graph.svg" width="500" />`
     good `<img src="https://raw.githubusercontent.com/owncloud/ocis/master/services/graph/images/mermaid-graph.svg" width="500" />`
-- [ ] If new CLI command are introduced, that command must be described in the README.md.
+- [ ] If new CLI commands are introduced, those commands must be described in the README.md.
 - [ ] If new global envvars are introduced, the name must start with `OCIS_`.
 - [ ] Add the service to the makefile in the ocis repo root.
 - [ ] Make the service startable for binary and individual startup:
@@ -35,12 +35,12 @@ Use this checklist with copy/paste in your PR - right from the beginning. It ren
   - The description must reflect the behaviour AND usually has a positive code quality impact.
 - [ ] Create proper description strings for envvars - see other services for examples, especially when it comes to multiple values. This must include:
   - base description, set of available values, description of each value.
-- [ ] When suggestable commits are created for text changes and you agree, collect them to a batch and commit them. Do not forget to rebase locally to avoid overwriting the changes made.
+- [ ] When suggested commits are created for text changes, and you agree, collect them to a batch and commit them. Do not forget to rebase locally to avoid overwriting the changes made.
 - [ ] 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/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.
 - [ ] Add metrics to the code to enable monitoring. See the proxy service for implementation details.
-  - Plus add a monitoring documentation in the README.md file
+  - Plus add documentation about monitoring in the README.md file
 ```
diff --git a/docs/services/general-info/port-ranges.md b/docs/services/general-info/port-ranges.md
index c51fadb680c..b0c54246cce 100644
--- a/docs/services/general-info/port-ranges.md
+++ b/docs/services/general-info/port-ranges.md
@@ -8,7 +8,7 @@ geekdocFilePath: port-ranges.md
 geekdocCollapseSection: true
 ---
 
-oCIS services often need a port to expose their services to other services or the outside world. As users may have many different extensions running on the same machine, we should track port usage in the oCIS ecosystem. In the best case we ensure that each extension uses a non colliding port range, to make life of users easier.
+oCIS services often need a port to expose their services to other services or the outside world. As users may have many different extensions running on the same machine, we should track port usage in the oCIS ecosystem. In the best case, we ensure that each extension uses a non-colliding port range, to make life easier for users.
 
 This page tracks the knowingly used port ranges.
 
@@ -16,7 +16,7 @@ Feel free to "reserve" a free port range when you're developing an extension by
 
 If you're developing a non-public extension, we recommend using ports outside of the ranges listed below.
 
-We also suggest to use the last port in your extensions' range as a debug/metrics port.
+We also suggest using the last port in your extensions' range as a debug/metrics port.
 
 ## Allocations
 
diff --git a/docs/services/general-info/registry.md b/docs/services/general-info/registry.md
index 5db99e82b31..124cef0b3ea 100644
--- a/docs/services/general-info/registry.md
+++ b/docs/services/general-info/registry.md
@@ -16,7 +16,7 @@ The type of registry to use can be configured with the `MICRO_REGISTRY` environm
 
 ### `memory`
 
-Setting the environment variable to `memory` starts an inmemory registry. This only works with the single binary deployment.
+Setting the environment variable to `memory` starts an in-memory registry. This only works with the single binary deployment.
 
 ### `nats-js-kv` (Default)
 
@@ -25,15 +25,15 @@ Set the environment variable to `nats-js-kv` or leave it empty to use a nats-js
 - Optional: Use `MICRO_REGISTRY_AUTH_USERNAME` and `MICRO_REGISTRY_AUTH_PASSWORD` to authenticate with the nats cluster.
 ### `kubernetes`
 
-When deploying in a kubernetes cluster, the kubernetes registry can be used. Additionally the `MICRO_REGISTRY_ADDRESS` environment
+When deploying in a kubernetes cluster, the kubernetes registry can be used. Additionally, the `MICRO_REGISTRY_ADDRESS` environment
 variable needs to be set to the url of the kubernetes registry.
 
 ### Deprecated Registries
 
 These registries are currently working but will be removed in a later version. It is recommended to switch to a supported one.
 
-- `nats`. Uses a registry based on nats streams. Requires `MICRO_REGISTRY_ADDRESS` to bet set.
-- `etcd`. Uses an etcd cluster as registry. Requires `MICRO_REGISTRY_ADDRESS` to bet set.
-- `consul`. Uses `HashiCorp Consul` as registry. Requires `MICRO_REGISTRY_ADDRESS` to bet set.
+- `nats`. Uses a registry based on nats streams. Requires `MICRO_REGISTRY_ADDRESS` to be set.
+- `etcd`. Uses an etcd cluster as the registry. Requires `MICRO_REGISTRY_ADDRESS` to be set.
+- `consul`. Uses `HashiCorp Consul` as registry. Requires `MICRO_REGISTRY_ADDRESS` to be set.
 - `mdns`.  Uses multicast dns for registration. This type can have unwanted side effects when other devices in the local network use mdns too.
 
diff --git a/go.mod b/go.mod
index f1094f7458b..e77add72de8 100644
--- a/go.mod
+++ b/go.mod
@@ -102,14 +102,14 @@ require (
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0
 	go.opentelemetry.io/otel/sdk v1.27.0
 	go.opentelemetry.io/otel/trace v1.27.0
-	golang.org/x/crypto v0.23.0
+	golang.org/x/crypto v0.24.0
 	golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3
 	golang.org/x/image v0.16.0
-	golang.org/x/net v0.25.0
+	golang.org/x/net v0.26.0
 	golang.org/x/oauth2 v0.20.0
 	golang.org/x/sync v0.7.0
-	golang.org/x/term v0.20.0
-	golang.org/x/text v0.15.0
+	golang.org/x/term v0.21.0
+	golang.org/x/text v0.16.0
 	google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5
 	google.golang.org/grpc v1.64.0
 	google.golang.org/protobuf v1.34.1
@@ -344,9 +344,9 @@ require (
 	go.uber.org/multierr v1.9.0 // indirect
 	go.uber.org/zap v1.23.0 // indirect
 	golang.org/x/mod v0.17.0 // indirect
-	golang.org/x/sys v0.20.0 // indirect
+	golang.org/x/sys v0.21.0 // indirect
 	golang.org/x/time v0.5.0 // indirect
-	golang.org/x/tools v0.21.0 // indirect
+	golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
diff --git a/go.sum b/go.sum
index fdb82ffb76d..3714d2e38ec 100644
--- a/go.sum
+++ b/go.sum
@@ -2205,8 +2205,8 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y
 golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
-golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
-golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
+golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
 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=
@@ -2352,8 +2352,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
-golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
-golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
 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=
@@ -2536,8 +2536,8 @@ 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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
+golang.org/x/sys v0.21.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=
@@ -2555,8 +2555,8 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
 golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
-golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
-golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
+golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
 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=
@@ -2578,8 +2578,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
-golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -2665,8 +2665,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
 golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
-golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
-golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 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/services/collaboration/pkg/config/app.go b/services/collaboration/pkg/config/app.go
index bbe08ef094e..a2cf83b19e5 100644
--- a/services/collaboration/pkg/config/app.go
+++ b/services/collaboration/pkg/config/app.go
@@ -2,11 +2,11 @@ package config
 
 // App defines the available app configuration.
 type App struct {
-	Name        string `yaml:"name" env:"COLLABORATION_APP_NAME" desc:"The name of the app" introductionVersion:"5.1"`
-	Description string `yaml:"description" env:"COLLABORATION_APP_DESCRIPTION" desc:"App description" introductionVersion:"5.1"`
-	Icon        string `yaml:"icon" env:"COLLABORATION_APP_ICON" desc:"Icon for the app" introductionVersion:"5.1"`
-	LockName    string `yaml:"lockname" env:"COLLABORATION_APP_LOCKNAME" desc:"Name for the app lock" introductionVersion:"5.1"`
+	Name        string `yaml:"name" env:"COLLABORATION_APP_NAME" desc:"The name of the app" introductionVersion:"%%NEXT%%"`
+	Description string `yaml:"description" env:"COLLABORATION_APP_DESCRIPTION" desc:"App description" introductionVersion:"%%NEXT%%"`
+	Icon        string `yaml:"icon" env:"COLLABORATION_APP_ICON" desc:"Icon for the app" introductionVersion:"%%NEXT%%"`
+	LockName    string `yaml:"lockname" env:"COLLABORATION_APP_LOCKNAME" desc:"Name for the app lock" introductionVersion:"%%NEXT%%"`
 
-	Addr     string `yaml:"addr" env:"COLLABORATION_APP_ADDR" desc:"The URL where the WOPI app is located, such as https://127.0.0.1:8080." introductionVersion:"5.1"`
-	Insecure bool   `yaml:"insecure" env:"COLLABORATION_APP_INSECURE" desc:"Skip TLS certificate verification when connecting to the WOPI app" introductionVersion:"5.1"`
+	Addr     string `yaml:"addr" env:"COLLABORATION_APP_ADDR" desc:"The URL where the WOPI app is located, such as https://127.0.0.1:8080." introductionVersion:"%%NEXT%%"`
+	Insecure bool   `yaml:"insecure" env:"COLLABORATION_APP_INSECURE" desc:"Skip TLS certificate verification when connecting to the WOPI app" introductionVersion:"%%NEXT%%"`
 }
diff --git a/services/collaboration/pkg/config/config.go b/services/collaboration/pkg/config/config.go
index 054529d94e3..fb95075c8a5 100644
--- a/services/collaboration/pkg/config/config.go
+++ b/services/collaboration/pkg/config/config.go
@@ -31,10 +31,10 @@ type Config struct {
 
 // Tracing defines the available tracing configuration. Not used at the moment
 type Tracing struct {
-	Enabled   bool   `yaml:"enabled" env:"OCIS_TRACING_ENABLED;COLLABORATION_TRACING_ENABLED" desc:"Activates tracing." introductionVersion:"5.1"`
-	Type      string `yaml:"type" env:"OCIS_TRACING_TYPE;COLLABORATION_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now." introductionVersion:"5.1"`
-	Endpoint  string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;COLLABORATION_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent." introductionVersion:"5.1"`
-	Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;COLLABORATION_TRACING_COLLECTOR" desc:"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." introductionVersion:"5.1"`
+	Enabled   bool   `yaml:"enabled" env:"OCIS_TRACING_ENABLED;COLLABORATION_TRACING_ENABLED" desc:"Activates tracing." introductionVersion:"%%NEXT%%"`
+	Type      string `yaml:"type" env:"OCIS_TRACING_TYPE;COLLABORATION_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now." introductionVersion:"%%NEXT%%"`
+	Endpoint  string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;COLLABORATION_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent." introductionVersion:"%%NEXT%%"`
+	Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;COLLABORATION_TRACING_COLLECTOR" desc:"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." introductionVersion:"%%NEXT%%"`
 }
 
 // Convert Tracing to the tracing package's Config struct.
diff --git a/services/collaboration/pkg/config/cs3api.go b/services/collaboration/pkg/config/cs3api.go
index d2a85f3e11b..1d6711e8c51 100644
--- a/services/collaboration/pkg/config/cs3api.go
+++ b/services/collaboration/pkg/config/cs3api.go
@@ -8,10 +8,10 @@ type CS3Api struct {
 
 // Gateway defines the available configuration for the CS3 API gateway
 type Gateway struct {
-	Name string `yaml:"name" env:"OCIS_REVA_GATEWAY;COLLABORATION_CS3API_GATEWAY_NAME" desc:"The service name of the CS3API gateway." introductionVersion:"5.1"`
+	Name string `yaml:"name" env:"OCIS_REVA_GATEWAY;COLLABORATION_CS3API_GATEWAY_NAME" desc:"The service name of the CS3API gateway." introductionVersion:"%%NEXT%%"`
 }
 
 // DataGateway defines the available configuration for the CS3 API data gateway
 type DataGateway struct {
-	Insecure bool `yaml:"insecure" env:"COLLABORATION_CS3API_DATAGATEWAY_INSECURE" desc:"Connect to the CS3API data gateway insecurely." introductionVersion:"5.1"`
+	Insecure bool `yaml:"insecure" env:"COLLABORATION_CS3API_DATAGATEWAY_INSECURE" desc:"Connect to the CS3API data gateway insecurely." introductionVersion:"%%NEXT%%"`
 }
diff --git a/services/collaboration/pkg/config/debug.go b/services/collaboration/pkg/config/debug.go
index 0961402e319..9b1eab8889e 100644
--- a/services/collaboration/pkg/config/debug.go
+++ b/services/collaboration/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration. Not used at the moment
 type Debug struct {
-	Addr   string `yaml:"addr" env:"COLLABORATION_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"5.1"`
-	Token  string `yaml:"token" env:"COLLABORATION_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"5.1"`
-	Pprof  bool   `yaml:"pprof" env:"COLLABORATION_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"5.1"`
-	Zpages bool   `yaml:"zpages" env:"COLLABORATION_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces." introductionVersion:"5.1"`
+	Addr   string `yaml:"addr" env:"COLLABORATION_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"%%NEXT%%"`
+	Token  string `yaml:"token" env:"COLLABORATION_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"%%NEXT%%"`
+	Pprof  bool   `yaml:"pprof" env:"COLLABORATION_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"%%NEXT%%"`
+	Zpages bool   `yaml:"zpages" env:"COLLABORATION_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces." introductionVersion:"%%NEXT%%"`
 }
diff --git a/services/collaboration/pkg/config/grpc.go b/services/collaboration/pkg/config/grpc.go
index b7351bd37ae..7e7044f3e22 100644
--- a/services/collaboration/pkg/config/grpc.go
+++ b/services/collaboration/pkg/config/grpc.go
@@ -2,6 +2,6 @@ package config
 
 // GRPC defines the available grpc configuration.
 type GRPC struct {
-	Addr      string `yaml:"addr" env:"COLLABORATION_GRPC_ADDR" desc:"The bind address of the GRPC service." introductionVersion:"5.1"`
+	Addr      string `yaml:"addr" env:"COLLABORATION_GRPC_ADDR" desc:"The bind address of the GRPC service." introductionVersion:"%%NEXT%%"`
 	Namespace string `yaml:"-"`
 }
diff --git a/services/collaboration/pkg/config/http.go b/services/collaboration/pkg/config/http.go
index ba8e6a92f0a..cee5a80a27c 100644
--- a/services/collaboration/pkg/config/http.go
+++ b/services/collaboration/pkg/config/http.go
@@ -6,7 +6,7 @@ import (
 
 // HTTP defines the available http configuration.
 type HTTP struct {
-	Addr      string                `yaml:"addr" env:"COLLABORATION_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"5.1"`
+	Addr      string                `yaml:"addr" env:"COLLABORATION_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"%%NEXT%%"`
 	Namespace string                `yaml:"-"`
 	TLS       shared.HTTPServiceTLS `yaml:"tls"`
 }
diff --git a/services/collaboration/pkg/config/log.go b/services/collaboration/pkg/config/log.go
index 2f6c7dbd9d6..aa4cc37f4f7 100644
--- a/services/collaboration/pkg/config/log.go
+++ b/services/collaboration/pkg/config/log.go
@@ -2,8 +2,8 @@ package config
 
 // Log defines the available log configuration.
 type Log struct {
-	Level  string `yaml:"level" env:"OCIS_LOG_LEVEL;COLLABORATION_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"5.1"`
-	Pretty bool   `yaml:"pretty" env:"OCIS_LOG_PRETTY;COLLABORATION_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"5.1"`
-	Color  bool   `yaml:"color" env:"OCIS_LOG_COLOR;COLLABORATION_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"5.1"`
-	File   string `yaml:"file" env:"OCIS_LOG_FILE;COLLABORATION_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"5.1"`
+	Level  string `yaml:"level" env:"OCIS_LOG_LEVEL;COLLABORATION_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"%%NEXT%%"`
+	Pretty bool   `yaml:"pretty" env:"OCIS_LOG_PRETTY;COLLABORATION_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"%%NEXT%%"`
+	Color  bool   `yaml:"color" env:"OCIS_LOG_COLOR;COLLABORATION_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"%%NEXT%%"`
+	File   string `yaml:"file" env:"OCIS_LOG_FILE;COLLABORATION_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"%%NEXT%%"`
 }
diff --git a/services/collaboration/pkg/config/wopi.go b/services/collaboration/pkg/config/wopi.go
index fd1b0e618a2..f74411464b5 100644
--- a/services/collaboration/pkg/config/wopi.go
+++ b/services/collaboration/pkg/config/wopi.go
@@ -2,6 +2,6 @@ package config
 
 // Wopi defines the available configuration for the WOPI endpoint.
 type Wopi struct {
-	WopiSrc string `yaml:"wopisrc" env:"COLLABORATION_WOPI_SRC" desc:"The WOPISrc base URL containing schema, host and port. Set this to the schema and domain where the collaboration service is reachable for the wopi app, such as https://office.owncloud.test." introductionVersion:"5.1"`
-	Secret  string `yaml:"secret" env:"COLLABORATION_WOPI_SECRET" desc:"Used to mint and verify WOPI JWT tokens and encrypt and decrypt the REVA JWT token embedded in the WOPI JWT token." introductionVersion:"5.1"`
+	WopiSrc string `yaml:"wopisrc" env:"COLLABORATION_WOPI_SRC" desc:"The WOPISrc base URL containing schema, host and port. Set this to the schema and domain where the collaboration service is reachable for the wopi app, such as https://office.owncloud.test." introductionVersion:"%%NEXT%%"`
+	Secret  string `yaml:"secret" env:"COLLABORATION_WOPI_SECRET" desc:"Used to mint and verify WOPI JWT tokens and encrypt and decrypt the REVA JWT token embedded in the WOPI JWT token." introductionVersion:"%%NEXT%%"`
 }
diff --git a/services/frontend/pkg/config/config.go b/services/frontend/pkg/config/config.go
index 9288577e49f..da8616b25e3 100644
--- a/services/frontend/pkg/config/config.go
+++ b/services/frontend/pkg/config/config.go
@@ -109,7 +109,7 @@ type Auth struct {
 type AppHandler struct {
 	Prefix            string `yaml:"-"`
 	Insecure          bool   `yaml:"insecure" env:"OCIS_INSECURE;FRONTEND_APP_HANDLER_INSECURE" desc:"Allow insecure connections to the frontend." introductionVersion:"pre5.0"`
-	SecureViewAppAddr string `yaml:"secure_view_app_addr" env:"FRONTEND_APP_HANDLER_SECURE_VIEW_APP_ADDR" desc:"Service name or address of the app provider to use for secure view. Should match the service name or address of the registered CS3 app provider." introductionVersion:"5.1"`
+	SecureViewAppAddr string `yaml:"secure_view_app_addr" env:"FRONTEND_APP_HANDLER_SECURE_VIEW_APP_ADDR" desc:"Service name or address of the app provider to use for secure view. Should match the service name or address of the registered CS3 app provider." introductionVersion:"%%NEXT%%"`
 }
 
 type Archiver struct {
@@ -145,7 +145,7 @@ type OCS struct {
 	PublicShareMustHavePassword          bool               `yaml:"public_sharing_share_must_have_password" env:"OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords on all public shares." introductionVersion:"5.0"`
 	WriteablePublicShareMustHavePassword bool               `yaml:"public_sharing_writeableshare_must_have_password" env:"OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords for writable shares. Only effective if the setting for 'passwords on all public shares' is set to false." introductionVersion:"5.0"`
 	IncludeOCMSharees                    bool               `yaml:"include_ocm_sharees" env:"FRONTEND_OCS_INCLUDE_OCM_SHAREES" desc:"Include OCM sharees when listing sharees." introductionVersion:"5.0"`
-	ShowUserEmailInResults               bool               `yaml:"show_email_in_results" env:"OCIS_SHOW_USER_EMAIL_IN_RESULTS" desc:"Mask user email addresses in responses." introductionVersion:"5.1"`
+	ShowUserEmailInResults               bool               `yaml:"show_email_in_results" env:"OCIS_SHOW_USER_EMAIL_IN_RESULTS" desc:"Mask user email addresses in responses." introductionVersion:"%%NEXT%%"`
 }
 
 type CacheWarmupDrivers struct {
diff --git a/services/graph/mocks/role_service.go b/services/graph/mocks/role_service.go
index 4538a943030..d9e19c16e7e 100644
--- a/services/graph/mocks/role_service.go
+++ b/services/graph/mocks/role_service.go
@@ -175,6 +175,80 @@ func (_c *RoleService_ListRoleAssignments_Call) RunAndReturn(run func(context.Co
 	return _c
 }
 
+// ListRoleAssignmentsFiltered provides a mock function with given fields: ctx, in, opts
+func (_m *RoleService) ListRoleAssignmentsFiltered(ctx context.Context, in *v0.ListRoleAssignmentsFilteredRequest, opts ...client.CallOption) (*v0.ListRoleAssignmentsResponse, error) {
+	_va := make([]interface{}, len(opts))
+	for _i := range opts {
+		_va[_i] = opts[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, ctx, in)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	if len(ret) == 0 {
+		panic("no return value specified for ListRoleAssignmentsFiltered")
+	}
+
+	var r0 *v0.ListRoleAssignmentsResponse
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *v0.ListRoleAssignmentsFilteredRequest, ...client.CallOption) (*v0.ListRoleAssignmentsResponse, error)); ok {
+		return rf(ctx, in, opts...)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *v0.ListRoleAssignmentsFilteredRequest, ...client.CallOption) *v0.ListRoleAssignmentsResponse); ok {
+		r0 = rf(ctx, in, opts...)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*v0.ListRoleAssignmentsResponse)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *v0.ListRoleAssignmentsFilteredRequest, ...client.CallOption) error); ok {
+		r1 = rf(ctx, in, opts...)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// RoleService_ListRoleAssignmentsFiltered_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRoleAssignmentsFiltered'
+type RoleService_ListRoleAssignmentsFiltered_Call struct {
+	*mock.Call
+}
+
+// ListRoleAssignmentsFiltered is a helper method to define mock.On call
+//   - ctx context.Context
+//   - in *v0.ListRoleAssignmentsFilteredRequest
+//   - opts ...client.CallOption
+func (_e *RoleService_Expecter) ListRoleAssignmentsFiltered(ctx interface{}, in interface{}, opts ...interface{}) *RoleService_ListRoleAssignmentsFiltered_Call {
+	return &RoleService_ListRoleAssignmentsFiltered_Call{Call: _e.mock.On("ListRoleAssignmentsFiltered",
+		append([]interface{}{ctx, in}, opts...)...)}
+}
+
+func (_c *RoleService_ListRoleAssignmentsFiltered_Call) Run(run func(ctx context.Context, in *v0.ListRoleAssignmentsFilteredRequest, opts ...client.CallOption)) *RoleService_ListRoleAssignmentsFiltered_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		variadicArgs := make([]client.CallOption, len(args)-2)
+		for i, a := range args[2:] {
+			if a != nil {
+				variadicArgs[i] = a.(client.CallOption)
+			}
+		}
+		run(args[0].(context.Context), args[1].(*v0.ListRoleAssignmentsFilteredRequest), variadicArgs...)
+	})
+	return _c
+}
+
+func (_c *RoleService_ListRoleAssignmentsFiltered_Call) Return(_a0 *v0.ListRoleAssignmentsResponse, _a1 error) *RoleService_ListRoleAssignmentsFiltered_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *RoleService_ListRoleAssignmentsFiltered_Call) RunAndReturn(run func(context.Context, *v0.ListRoleAssignmentsFilteredRequest, ...client.CallOption) (*v0.ListRoleAssignmentsResponse, error)) *RoleService_ListRoleAssignmentsFiltered_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // ListRoles provides a mock function with given fields: ctx, in, opts
 func (_m *RoleService) ListRoles(ctx context.Context, in *v0.ListBundlesRequest, opts ...client.CallOption) (*v0.ListBundlesResponse, error) {
 	_va := make([]interface{}, len(opts))
diff --git a/services/graph/pkg/config/config.go b/services/graph/pkg/config/config.go
index 6d2bfb426da..8da0109ea4c 100644
--- a/services/graph/pkg/config/config.go
+++ b/services/graph/pkg/config/config.go
@@ -112,7 +112,7 @@ type API struct {
 	UsernameMatch           string `yaml:"graph_username_match" env:"GRAPH_USERNAME_MATCH" desc:"Apply restrictions to usernames. Supported values are 'default' and 'none'. When set to 'default', user names must not start with a number and are restricted to ASCII characters. When set to 'none', no restrictions are applied. The default value is 'default'." introductionVersion:"pre5.0"`
 	AssignDefaultUserRole   bool   `yaml:"graph_assign_default_user_role" env:"GRAPH_ASSIGN_DEFAULT_USER_ROLE" desc:"Whether to assign newly created users the default role 'User'. Set this to 'false' if you want to assign roles manually, or if the role assignment should happen at first login. Set this to 'true' (the default) to assign the role 'User' when creating a new user." introductionVersion:"pre5.0"`
 	IdentitySearchMinLength int    `yaml:"graph_identity_search_min_length" env:"GRAPH_IDENTITY_SEARCH_MIN_LENGTH" desc:"The minimum length the search term needs to have for unprivileged users when searching for users or groups." introductionVersion:"5.0"`
-	ShowUserEmailInResults  bool   `yaml:"show_email_in_results" env:"OCIS_SHOW_USER_EMAIL_IN_RESULTS" desc:"Mask user email addresses in responses." introductionVersion:"5.1"`
+	ShowUserEmailInResults  bool   `yaml:"show_email_in_results" env:"OCIS_SHOW_USER_EMAIL_IN_RESULTS" desc:"Mask user email addresses in responses." introductionVersion:"%%NEXT%%"`
 }
 
 // Events combines the configuration options for the event bus.
diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go
index 258dc847d0f..b19313baae0 100644
--- a/services/graph/pkg/service/v0/users.go
+++ b/services/graph/pkg/service/v0/users.go
@@ -82,7 +82,7 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// expand appRoleAssignments if requested
-	if slices.Contains(exp, "appRoleAssignments") {
+	if slices.Contains(exp, appRoleAssignments) {
 		var err error
 		me.AppRoleAssignments, err = g.fetchAppRoleAssignments(r.Context(), me.GetId())
 		if err != nil {
@@ -295,10 +295,10 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
 		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
 		return
 	}
-	expandAppRoleAssignments := slices.Contains(exp, "appRoleAssignments")
+	expandAppRoleAssignments := slices.Contains(exp, appRoleAssignments)
 	expandMemberOf := slices.Contains(exp, "memberOf")
 	for _, u := range users {
-		if expandAppRoleAssignments {
+		if expandAppRoleAssignments && u.AppRoleAssignments == nil {
 			u.AppRoleAssignments, err = g.fetchAppRoleAssignments(r.Context(), u.GetId())
 			if err != nil {
 				// TODO I think we should not continue here, see http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionexpand
@@ -539,7 +539,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// expand appRoleAssignments if requested
-	if slices.Contains(exp, "appRoleAssignments") {
+	if slices.Contains(exp, appRoleAssignments) {
 		user.AppRoleAssignments, err = g.fetchAppRoleAssignments(r.Context(), user.GetId())
 		if err != nil {
 			logger.Debug().Err(err).Str("userid", user.GetId()).Msg("could not get appRoleAssignments for user")
diff --git a/services/graph/pkg/service/v0/users_filter.go b/services/graph/pkg/service/v0/users_filter.go
index fd3768b27b9..e3a23ebc216 100644
--- a/services/graph/pkg/service/v0/users_filter.go
+++ b/services/graph/pkg/service/v0/users_filter.go
@@ -6,10 +6,13 @@ import (
 
 	"github.com/CiscoM31/godata"
 	libregraph "github.com/owncloud/libre-graph-api-go"
+	settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
+	settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
 )
 
 const (
-	appRoleID = "appRoleId"
+	appRoleID          = "appRoleId"
+	appRoleAssignments = "appRoleAssignments"
 )
 
 func invalidFilterError() error {
@@ -156,7 +159,7 @@ func (g Graph) applyFilterLogicalAnd(ctx context.Context, req *godata.GoDataRequ
 			return []*libregraph.User{}, err
 		}
 		logger.Debug().Str("property", property).Str("value", value).Msg("applying approleAssignments filter on result set")
-		return g.filterUsersByAppRoleID(ctx, value, res2)
+		return g.filterUsersByAppRoleID(ctx, req, value, res2)
 	}
 
 	// 1st part is no appRoleAssignmentFilter, run the filter query
@@ -172,7 +175,7 @@ func (g Graph) applyFilterLogicalAnd(ctx context.Context, req *godata.GoDataRequ
 			return users, unsupportedFilterError()
 		}
 		logger.Debug().Str("property", property).Str("value", value).Msg("applying approleAssignments filter on result set")
-		return g.filterUsersByAppRoleID(ctx, value, res1)
+		return g.filterUsersByAppRoleID(ctx, req, value, res1)
 	}
 
 	// 2nd part is no appRoleAssignmentFilter either
@@ -235,7 +238,7 @@ func (g Graph) applyFilterLambda(ctx context.Context, req *godata.GoDataRequest,
 	switch nodes[0].Token.Value {
 	case "memberOf":
 		return g.applyLambdaMemberOfAny(ctx, req, nodes[1].Children)
-	case "appRoleAssignments":
+	case appRoleAssignments:
 		return g.applyLambdaAppRoleAssignmentAny(ctx, req, nodes[1].Children)
 	}
 	logger.Debug().Str("Token", nodes[0].Token.Value).Msg("unsupported relation for lambda filter")
@@ -329,33 +332,53 @@ func (g Graph) applyAppRoleAssignmentEq(ctx context.Context, req *godata.GoDataR
 			return users, err
 		}
 
-		return g.filterUsersByAppRoleID(ctx, filterValue, users)
+		return g.filterUsersByAppRoleID(ctx, req, filterValue, users)
 	}
 	return users, unsupportedFilterError()
 }
 
-func (g Graph) filterUsersByAppRoleID(ctx context.Context, id string, users []*libregraph.User) ([]*libregraph.User, error) {
+func (g Graph) filterUsersByAppRoleID(ctx context.Context, req *godata.GoDataRequest, id string, users []*libregraph.User) ([]*libregraph.User, error) {
 	// We're using a map for the results here, in order to avoid returning
 	// a user twice. The settings API, still has an issue that causes it to
 	// duplicate some assignments on restart:
 	// https://github.com/owncloud/ocis/issues/3432
-	resultUsersMap := make(map[string]*libregraph.User, len(users))
+	usersByIdMap := make(map[string]*libregraph.User, len(users))
 	for _, user := range users {
-		assignments, err := g.fetchAppRoleAssignments(ctx, user.GetId())
-		if err != nil {
-			return users, err
-		}
-		for _, assignment := range assignments {
-			if assignment.GetAppRoleId() == id {
-				if _, ok := resultUsersMap[user.GetId()]; !ok {
-					resultUsersMap[user.GetId()] = user
-				}
+		usersByIdMap[user.GetId()] = user
+	}
+
+	var expand bool
+	if exp := req.Query.GetExpand(); exp != nil {
+		for _, item := range exp.ExpandItems {
+			if item.Path[0].Value == appRoleAssignments {
+				expand = true
+				break
 			}
 		}
 	}
-	resultUsers := make([]*libregraph.User, 0, len(resultUsersMap))
-	for _, user := range resultUsersMap {
-		resultUsers = append(resultUsers, user)
+
+	assignmentsForRole, err := g.roleService.ListRoleAssignmentsFiltered(
+		ctx,
+		&settingssvc.ListRoleAssignmentsFilteredRequest{
+			Filters: []*settingsmsg.UserRoleAssignmentFilter{
+				{
+					Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE,
+					Term: &settingsmsg.UserRoleAssignmentFilter_RoleId{RoleId: id},
+				},
+			},
+		},
+	)
+	if err != nil {
+		return users, err
+	}
+	resultUsers := make([]*libregraph.User, 0, len(assignmentsForRole.GetAssignments()))
+	for _, assignment := range assignmentsForRole.GetAssignments() {
+		if user, ok := usersByIdMap[assignment.GetAccountUuid()]; ok {
+			if expand {
+				user.AppRoleAssignments = []libregraph.AppRoleAssignment{g.assignmentToAppRoleAssignment(assignment)}
+			}
+			resultUsers = append(resultUsers, user)
+		}
 	}
 	return resultUsers, nil
 }
@@ -383,7 +406,7 @@ func (g Graph) isAppRoleAssignmentFilter(ctx context.Context, node *godata.Parse
 		return false, "", ""
 	}
 
-	if node.Children[0].Token.Type != godata.ExpressionTokenLiteral || node.Children[0].Token.Value != "appRoleAssignments" {
+	if node.Children[0].Token.Type != godata.ExpressionTokenLiteral || node.Children[0].Token.Value != appRoleAssignments {
 		return false, "", ""
 	}
 
diff --git a/services/notifications/pkg/email/l10n/locale/ja_JP/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/ja_JP/LC_MESSAGES/notifications.po
index 73d33711986..ae4cae79226 100644
--- a/services/notifications/pkg/email/l10n/locale/ja_JP/LC_MESSAGES/notifications.po
+++ b/services/notifications/pkg/email/l10n/locale/ja_JP/LC_MESSAGES/notifications.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-05-17 00:09+0000\n"
+"POT-Creation-Date: 2024-06-06 00:46+0000\n"
 "PO-Revision-Date: 2023-04-19 11:11+0000\n"
 "Last-Translator: hoshi1800 <hoshi1800@gmail.com>, 2024\n"
 "Language-Team: Japanese (Japan) (https://app.transifex.com/owncloud-org/teams/6149/ja_JP/)\n"
diff --git a/services/proxy/pkg/config/config.go b/services/proxy/pkg/config/config.go
index c0cb49a72d1..833adae1c46 100644
--- a/services/proxy/pkg/config/config.go
+++ b/services/proxy/pkg/config/config.go
@@ -43,7 +43,7 @@ type Config struct {
 	BackendHTTPSCACert    string              `yaml:"backend_https_cacert" env:"PROXY_HTTPS_CACERT" desc:"Path/File for the root CA certificate used to validate the server’s TLS certificate for https enabled backend services." introductionVersion:"pre5.0"`
 	AuthMiddleware        AuthMiddleware      `yaml:"auth_middleware"`
 	PoliciesMiddleware    PoliciesMiddleware  `yaml:"policies_middleware"`
-	CSPConfigFileLocation string              `yaml:"csp_config_file_location" env:"PROXY_CSP_CONFIG_FILE_LOCATION" desc:"The location of the CSP configuration file." introductionVersion:"6.0"`
+	CSPConfigFileLocation string              `yaml:"csp_config_file_location" env:"PROXY_CSP_CONFIG_FILE_LOCATION" desc:"The location of the CSP configuration file." introductionVersion:"%%NEXT%%"`
 
 	Context context.Context `yaml:"-" json:"-"`
 }
@@ -156,9 +156,9 @@ type RoleMapping struct {
 
 // AutoProvisionClaims defines which claims from the OIDC userinfo response should be used for auto-provisioning user accounts
 type AutoProvisionClaims struct {
-	Username    string `yaml:"username" env:"PROXY_AUTOPROVISION_CLAIM_USERNAME" desc:"The name of the OIDC claim that holds the username." introductionVersion:"5.1"`
-	Email       string `yaml:"email" env:"PROXY_AUTOPROVISION_CLAIM_EMAIL" desc:"The name of the OIDC claim that holds the email." introductionVersion:"5.1"`
-	DisplayName string `yaml:"display_name" env:"PROXY_AUTOPROVISION_CLAIM_DISPLAYNAME" desc:"The name of the OIDC claim that holds the display name." introductionVersion:"5.1"`
+	Username    string `yaml:"username" env:"PROXY_AUTOPROVISION_CLAIM_USERNAME" desc:"The name of the OIDC claim that holds the username." introductionVersion:"%%NEXT%%"`
+	Email       string `yaml:"email" env:"PROXY_AUTOPROVISION_CLAIM_EMAIL" desc:"The name of the OIDC claim that holds the email." introductionVersion:"%%NEXT%%"`
+	DisplayName string `yaml:"display_name" env:"PROXY_AUTOPROVISION_CLAIM_DISPLAYNAME" desc:"The name of the OIDC claim that holds the display name." introductionVersion:"%%NEXT%%"`
 }
 
 // PolicySelector is the toplevel-configuration for different selectors
diff --git a/services/storage-users/pkg/config/config.go b/services/storage-users/pkg/config/config.go
index cfff6717716..6c3f2e6efb7 100644
--- a/services/storage-users/pkg/config/config.go
+++ b/services/storage-users/pkg/config/config.go
@@ -126,11 +126,11 @@ type OCISDriver struct {
 	// PersonalSpaceAliasTemplate  contains the template used to construct
 	// the personal space alias, eg: `"{{.SpaceType}}/{{.User.Username | lower}}"`
 	PersonalSpaceAliasTemplate string `yaml:"personalspacealias_template" env:"STORAGE_USERS_OCIS_PERSONAL_SPACE_ALIAS_TEMPLATE" desc:"Template string to construct personal space aliases." introductionVersion:"pre5.0"`
-	PersonalSpacePathTemplate  string `yaml:"personalspacepath_template" env:"STORAGE_USERS_OCIS_PERSONAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the personal space roots." introductionVersion:"6.0"`
+	PersonalSpacePathTemplate  string `yaml:"personalspacepath_template" env:"STORAGE_USERS_OCIS_PERSONAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the personal space roots." introductionVersion:"%%NEXT%%"`
 	// GeneralSpaceAliasTemplate contains the template used to construct
 	// the general space alias, eg: `{{.SpaceType}}/{{.SpaceName | replace " " "-" | lower}}`
 	GeneralSpaceAliasTemplate string `yaml:"generalspacealias_template" env:"STORAGE_USERS_OCIS_GENERAL_SPACE_ALIAS_TEMPLATE" desc:"Template string to construct general space aliases." introductionVersion:"pre5.0"`
-	GeneralSpacePathTemplate  string `yaml:"generalspacepath_template" env:"STORAGE_USERS_OCIS_GENERAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the projects space roots." introductionVersion:"6.0"`
+	GeneralSpacePathTemplate  string `yaml:"generalspacepath_template" env:"STORAGE_USERS_OCIS_GENERAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the projects space roots." introductionVersion:"%%NEXT%%"`
 	// ShareFolder defines the name of the folder jailing all shares
 	ShareFolder             string `yaml:"share_folder" env:"STORAGE_USERS_OCIS_SHARE_FOLDER" desc:"Name of the folder jailing all shares." introductionVersion:"pre5.0"`
 	MaxAcquireLockCycles    int    `yaml:"max_acquire_lock_cycles" env:"STORAGE_USERS_OCIS_MAX_ACQUIRE_LOCK_CYCLES" desc:"When trying to lock files, ocis will try this amount of times to acquire the lock before failing. After each try it will wait for an increasing amount of time. Values of 0 or below will be ignored and the default value will be used." introductionVersion:"pre5.0"`
@@ -163,11 +163,11 @@ type S3NGDriver struct {
 	// PersonalSpaceAliasTemplate  contains the template used to construct
 	// the personal space alias, eg: `"{{.SpaceType}}/{{.User.Username | lower}}"`
 	PersonalSpaceAliasTemplate string `yaml:"personalspacealias_template" env:"STORAGE_USERS_S3NG_PERSONAL_SPACE_ALIAS_TEMPLATE" desc:"Template string to construct personal space aliases." introductionVersion:"pre5.0"`
-	PersonalSpacePathTemplate  string `yaml:"personalspacepath_template" env:"STORAGE_USERS_S3NG_PERSONAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the personal space roots." introductionVersion:"6.0"`
+	PersonalSpacePathTemplate  string `yaml:"personalspacepath_template" env:"STORAGE_USERS_S3NG_PERSONAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the personal space roots." introductionVersion:"%%NEXT%%"`
 	// GeneralSpaceAliasTemplate contains the template used to construct
 	// the general space alias, eg: `{{.SpaceType}}/{{.SpaceName | replace " " "-" | lower}}`
 	GeneralSpaceAliasTemplate string `yaml:"generalspacealias_template" env:"STORAGE_USERS_S3NG_GENERAL_SPACE_ALIAS_TEMPLATE" desc:"Template string to construct general space aliases." introductionVersion:"pre5.0"`
-	GeneralSpacePathTemplate  string `yaml:"generalspacepath_template" env:"STORAGE_USERS_S3NG_GENERAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the projects space roots." introductionVersion:"6.0"`
+	GeneralSpacePathTemplate  string `yaml:"generalspacepath_template" env:"STORAGE_USERS_S3NG_GENERAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the projects space roots." introductionVersion:"%%NEXT%%"`
 	// ShareFolder defines the name of the folder jailing all shares
 	ShareFolder             string `yaml:"share_folder" env:"STORAGE_USERS_S3NG_SHARE_FOLDER" desc:"Name of the folder jailing all shares." introductionVersion:"pre5.0"`
 	MaxAcquireLockCycles    int    `yaml:"max_acquire_lock_cycles" env:"STORAGE_USERS_S3NG_MAX_ACQUIRE_LOCK_CYCLES" desc:"When trying to lock files, ocis will try this amount of times to acquire the lock before failing. After each try it will wait for an increasing amount of time. Values of 0 or below will be ignored and the default value of 20 will be used." introductionVersion:"pre5.0"`
@@ -194,16 +194,16 @@ type OwnCloudSQLDriver struct {
 // PosixDriver is the storage driver configuration when using 'posix' storage driver
 type PosixDriver struct {
 	// Root is the absolute path to the location of the data
-	Root                      string `yaml:"root" env:"STORAGE_USERS_POSIX_ROOT" desc:"The directory where the filesystem storage will store its data. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage/owncloud." introductionVersion:"6.0"`
-	PersonalSpacePathTemplate string `yaml:"personalspacepath_template" env:"STORAGE_USERS_POSIX_PERSONAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the personal space roots." introductionVersion:"6.0"`
-	GeneralSpacePathTemplate  string `yaml:"generalspacepath_template" env:"STORAGE_USERS_POSIX_GENERAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the projects space roots." introductionVersion:"6.0"`
-	PermissionsEndpoint       string `yaml:"permissions_endpoint" env:"STORAGE_USERS_PERMISSION_ENDPOINT;STORAGE_USERS_POSIX_PERMISSIONS_ENDPOINT" desc:"Endpoint of the permissions service. The endpoints can differ for 'ocis', 'posix' and 's3ng'." introductionVersion:"6.0"`
+	Root                      string `yaml:"root" env:"STORAGE_USERS_POSIX_ROOT" desc:"The directory where the filesystem storage will store its data. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage/owncloud." introductionVersion:"%%NEXT%%"`
+	PersonalSpacePathTemplate string `yaml:"personalspacepath_template" env:"STORAGE_USERS_POSIX_PERSONAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the personal space roots." introductionVersion:"%%NEXT%%"`
+	GeneralSpacePathTemplate  string `yaml:"generalspacepath_template" env:"STORAGE_USERS_POSIX_GENERAL_SPACE_PATH_TEMPLATE" desc:"Template string to construct the paths of the projects space roots." introductionVersion:"%%NEXT%%"`
+	PermissionsEndpoint       string `yaml:"permissions_endpoint" env:"STORAGE_USERS_PERMISSION_ENDPOINT;STORAGE_USERS_POSIX_PERMISSIONS_ENDPOINT" desc:"Endpoint of the permissions service. The endpoints can differ for 'ocis', 'posix' and 's3ng'." introductionVersion:"%%NEXT%%"`
 
-	UseSpaceGroups bool `yaml:"use_space_groups" env:"STORAGE_USERS_POSIX_USE_SPACE_GROUPS" desc:"Use space groups to manage permissions on spaces." introductionVersion:"6.0"`
+	UseSpaceGroups bool `yaml:"use_space_groups" env:"STORAGE_USERS_POSIX_USE_SPACE_GROUPS" desc:"Use space groups to manage permissions on spaces." introductionVersion:"%%NEXT%%"`
 
-	WatchType               string `yaml:"watch_type" env:"STORAGE_USERS_POSIX_WATCH_TYPE" desc:"Type of the watcher to use for getting notified about changes to the filesystem. Currently available options are 'inotifywait' (default), 'gpfswatchfolder' and 'gpfsfileauditlogging'." introductionVersion:"6.0"`
-	WatchPath               string `yaml:"watch_path" env:"STORAGE_USERS_POSIX_WATCH_PATH" desc:"Path to the watch directory/file. Only applies to the 'gpfsfileauditlogging' and 'inotifywait' watcher, in which case it is the path of the file audit log file/base directory to watch." introductionVersion:"6.0"`
-	WatchFolderKafkaBrokers string `yaml:"watch_folder_kafka_hosts" env:"STORAGE_USERS_POSIX_WATCH_FOLDER_KAFKA_BROKERS" desc:"Comma-separated list of kafka brokers to read the watchfolder events from." introductionVersion:"6.0"`
+	WatchType               string `yaml:"watch_type" env:"STORAGE_USERS_POSIX_WATCH_TYPE" desc:"Type of the watcher to use for getting notified about changes to the filesystem. Currently available options are 'inotifywait' (default), 'gpfswatchfolder' and 'gpfsfileauditlogging'." introductionVersion:"%%NEXT%%"`
+	WatchPath               string `yaml:"watch_path" env:"STORAGE_USERS_POSIX_WATCH_PATH" desc:"Path to the watch directory/file. Only applies to the 'gpfsfileauditlogging' and 'inotifywait' watcher, in which case it is the path of the file audit log file/base directory to watch." introductionVersion:"%%NEXT%%"`
+	WatchFolderKafkaBrokers string `yaml:"watch_folder_kafka_hosts" env:"STORAGE_USERS_POSIX_WATCH_FOLDER_KAFKA_BROKERS" desc:"Comma-separated list of kafka brokers to read the watchfolder events from." introductionVersion:"%%NEXT%%"`
 }
 
 // Events combines the configuration options for the event bus.
diff --git a/services/thumbnails/pkg/config/config.go b/services/thumbnails/pkg/config/config.go
index fa1c3122d3b..351c28017c7 100644
--- a/services/thumbnails/pkg/config/config.go
+++ b/services/thumbnails/pkg/config/config.go
@@ -44,7 +44,7 @@ type Thumbnail struct {
 	FontMapFile           string            `yaml:"font_map_file" env:"THUMBNAILS_TXT_FONTMAP_FILE" desc:"The path to a font file for txt thumbnails." introductionVersion:"pre5.0"`
 	TransferSecret        string            `yaml:"transfer_secret" env:"THUMBNAILS_TRANSFER_TOKEN" desc:"The secret to sign JWT to download the actual thumbnail file." introductionVersion:"pre5.0"`
 	DataEndpoint          string            `yaml:"data_endpoint" env:"THUMBNAILS_DATA_ENDPOINT" desc:"The HTTP endpoint where the actual thumbnail file can be downloaded." introductionVersion:"pre5.0"`
-	MaxInputWidth         int               `yaml:"max_input_width" env:"THUMBNAILS_MAX_INPUT_WIDTH" desc:"The maximum width of an input image which is being processed." introductionVersion:"6.0"`
-	MaxInputHeight        int               `yaml:"max_input_height" env:"THUMBNAILS_MAX_INPUT_HEIGHT" desc:"The maximum height of an input image which is being processed." introductionVersion:"6.0"`
-	MaxInputImageFileSize string            `yaml:"max_input_image_file_size" env:"THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE" desc:"The maximum file size of an input image which is being processed. Usable common abbreviations: [KB, KiB, MB, MiB, GB, GiB, TB, TiB, PB, PiB, EB, EiB], example: 2GB." introductionVersion:"6.0"`
+	MaxInputWidth         int               `yaml:"max_input_width" env:"THUMBNAILS_MAX_INPUT_WIDTH" desc:"The maximum width of an input image which is being processed." introductionVersion:"%%NEXT%%"`
+	MaxInputHeight        int               `yaml:"max_input_height" env:"THUMBNAILS_MAX_INPUT_HEIGHT" desc:"The maximum height of an input image which is being processed." introductionVersion:"%%NEXT%%"`
+	MaxInputImageFileSize string            `yaml:"max_input_image_file_size" env:"THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE" desc:"The maximum file size of an input image which is being processed. Usable common abbreviations: [KB, KiB, MB, MiB, GB, GiB, TB, TiB, PB, PiB, EB, EiB], example: 2GB." introductionVersion:"%%NEXT%%"`
 }
diff --git a/services/thumbnails/pkg/config/http.go b/services/thumbnails/pkg/config/http.go
index 1e779d2e725..ce18d17b435 100644
--- a/services/thumbnails/pkg/config/http.go
+++ b/services/thumbnails/pkg/config/http.go
@@ -8,5 +8,5 @@ type HTTP struct {
 	TLS                   shared.HTTPServiceTLS `yaml:"tls"`
 	Root                  string                `yaml:"root" env:"THUMBNAILS_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service." introductionVersion:"pre5.0"`
 	Namespace             string                `yaml:"-"`
-	MaxConcurrentRequests int                   `yaml:"max_concurrent_requests" env:"THUMBNAILS_MAX_CONCURRENT_REQUESTS" desc:"Number of maximum concurrent thumbnail requests. Default is 0 which is unlimited." introductionVersion:"6.0"`
+	MaxConcurrentRequests int                   `yaml:"max_concurrent_requests" env:"THUMBNAILS_MAX_CONCURRENT_REQUESTS" desc:"Number of maximum concurrent thumbnail requests. Default is 0 which is unlimited." introductionVersion:"%%NEXT%%"`
 }
diff --git a/services/web/pkg/config/config.go b/services/web/pkg/config/config.go
index 1fdd3422941..aceaceda72d 100644
--- a/services/web/pkg/config/config.go
+++ b/services/web/pkg/config/config.go
@@ -32,9 +32,9 @@ type Config struct {
 // Asset defines the available asset configuration.
 type Asset struct {
 	DeprecatedPath string `yaml:"path" env:"WEB_ASSET_PATH" desc:"Serve ownCloud Web assets from a path on the filesystem instead of the builtin assets." introductionVersion:"pre5.0" deprecationVersion:"5.1.0" removalVersion:"6.0.0" deprecationInfo:"The WEB_ASSET_PATH is deprecated and will be removed in the future." deprecationReplacement:"Use WEB_ASSET_CORE_PATH instead."`
-	CorePath       string `yaml:"core_path" env:"WEB_ASSET_CORE_PATH" desc:"Serve ownCloud Web assets from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/web/assets/core" introductionVersion:"5.1"`
-	ThemesPath     string `yaml:"themes_path" env:"OCIS_ASSET_THEMES_PATH;WEB_ASSET_THEMES_PATH" desc:"Serve ownCloud themes from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/web/assets/themes" introductionVersion:"5.1"`
-	AppsPath       string `yaml:"apps_path" env:"WEB_ASSET_APPS_PATH" desc:"Serve ownCloud Web apps assets from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/web/assets/apps" introductionVersion:"5.1"`
+	CorePath       string `yaml:"core_path" env:"WEB_ASSET_CORE_PATH" desc:"Serve ownCloud Web assets from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/web/assets/core" introductionVersion:"%%NEXT%%"`
+	ThemesPath     string `yaml:"themes_path" env:"OCIS_ASSET_THEMES_PATH;WEB_ASSET_THEMES_PATH" desc:"Serve ownCloud themes from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/web/assets/themes" introductionVersion:"%%NEXT%%"`
+	AppsPath       string `yaml:"apps_path" env:"WEB_ASSET_APPS_PATH" desc:"Serve ownCloud Web apps assets from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/web/assets/apps" introductionVersion:"%%NEXT%%"`
 }
 
 // CustomStyle references additional css to be loaded into ownCloud Web.
diff --git a/vendor/golang.org/x/crypto/sha3/hashes.go b/vendor/golang.org/x/crypto/sha3/hashes.go
index 0d8043fd2a1..5eae6cb922f 100644
--- a/vendor/golang.org/x/crypto/sha3/hashes.go
+++ b/vendor/golang.org/x/crypto/sha3/hashes.go
@@ -16,39 +16,43 @@ import (
 // Its generic security strength is 224 bits against preimage attacks,
 // and 112 bits against collision attacks.
 func New224() hash.Hash {
-	if h := new224Asm(); h != nil {
-		return h
-	}
-	return &state{rate: 144, outputLen: 28, dsbyte: 0x06}
+	return new224()
 }
 
 // New256 creates a new SHA3-256 hash.
 // Its generic security strength is 256 bits against preimage attacks,
 // and 128 bits against collision attacks.
 func New256() hash.Hash {
-	if h := new256Asm(); h != nil {
-		return h
-	}
-	return &state{rate: 136, outputLen: 32, dsbyte: 0x06}
+	return new256()
 }
 
 // New384 creates a new SHA3-384 hash.
 // Its generic security strength is 384 bits against preimage attacks,
 // and 192 bits against collision attacks.
 func New384() hash.Hash {
-	if h := new384Asm(); h != nil {
-		return h
-	}
-	return &state{rate: 104, outputLen: 48, dsbyte: 0x06}
+	return new384()
 }
 
 // New512 creates a new SHA3-512 hash.
 // Its generic security strength is 512 bits against preimage attacks,
 // and 256 bits against collision attacks.
 func New512() hash.Hash {
-	if h := new512Asm(); h != nil {
-		return h
-	}
+	return new512()
+}
+
+func new224Generic() *state {
+	return &state{rate: 144, outputLen: 28, dsbyte: 0x06}
+}
+
+func new256Generic() *state {
+	return &state{rate: 136, outputLen: 32, dsbyte: 0x06}
+}
+
+func new384Generic() *state {
+	return &state{rate: 104, outputLen: 48, dsbyte: 0x06}
+}
+
+func new512Generic() *state {
 	return &state{rate: 72, outputLen: 64, dsbyte: 0x06}
 }
 
diff --git a/vendor/golang.org/x/crypto/sha3/hashes_generic.go b/vendor/golang.org/x/crypto/sha3/hashes_generic.go
deleted file mode 100644
index fe8c84793c0..00000000000
--- a/vendor/golang.org/x/crypto/sha3/hashes_generic.go
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2017 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 !gc || purego || !s390x
-
-package sha3
-
-import (
-	"hash"
-)
-
-// new224Asm returns an assembly implementation of SHA3-224 if available,
-// otherwise it returns nil.
-func new224Asm() hash.Hash { return nil }
-
-// new256Asm returns an assembly implementation of SHA3-256 if available,
-// otherwise it returns nil.
-func new256Asm() hash.Hash { return nil }
-
-// new384Asm returns an assembly implementation of SHA3-384 if available,
-// otherwise it returns nil.
-func new384Asm() hash.Hash { return nil }
-
-// new512Asm returns an assembly implementation of SHA3-512 if available,
-// otherwise it returns nil.
-func new512Asm() hash.Hash { return nil }
diff --git a/vendor/golang.org/x/crypto/sha3/hashes_noasm.go b/vendor/golang.org/x/crypto/sha3/hashes_noasm.go
new file mode 100644
index 00000000000..9d85fb62144
--- /dev/null
+++ b/vendor/golang.org/x/crypto/sha3/hashes_noasm.go
@@ -0,0 +1,23 @@
+// Copyright 2023 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 !gc || purego || !s390x
+
+package sha3
+
+func new224() *state {
+	return new224Generic()
+}
+
+func new256() *state {
+	return new256Generic()
+}
+
+func new384() *state {
+	return new384Generic()
+}
+
+func new512() *state {
+	return new512Generic()
+}
diff --git a/vendor/golang.org/x/crypto/sha3/sha3.go b/vendor/golang.org/x/crypto/sha3/sha3.go
index 4884d172a49..afedde5abf1 100644
--- a/vendor/golang.org/x/crypto/sha3/sha3.go
+++ b/vendor/golang.org/x/crypto/sha3/sha3.go
@@ -23,7 +23,6 @@ const (
 type state struct {
 	// Generic sponge components.
 	a    [25]uint64 // main state of the hash
-	buf  []byte     // points into storage
 	rate int        // the number of bytes of state to use
 
 	// dsbyte contains the "domain separation" bits and the first bit of
@@ -40,7 +39,8 @@ type state struct {
 	//      Extendable-Output Functions (May 2014)"
 	dsbyte byte
 
-	storage storageBuf
+	i, n    int // storage[i:n] is the buffer, i is only used while squeezing
+	storage [maxRate]byte
 
 	// Specific to SHA-3 and SHAKE.
 	outputLen int             // the default output size in bytes
@@ -54,24 +54,18 @@ func (d *state) BlockSize() int { return d.rate }
 func (d *state) Size() int { return d.outputLen }
 
 // Reset clears the internal state by zeroing the sponge state and
-// the byte buffer, and setting Sponge.state to absorbing.
+// the buffer indexes, and setting Sponge.state to absorbing.
 func (d *state) Reset() {
 	// Zero the permutation's state.
 	for i := range d.a {
 		d.a[i] = 0
 	}
 	d.state = spongeAbsorbing
-	d.buf = d.storage.asBytes()[:0]
+	d.i, d.n = 0, 0
 }
 
 func (d *state) clone() *state {
 	ret := *d
-	if ret.state == spongeAbsorbing {
-		ret.buf = ret.storage.asBytes()[:len(ret.buf)]
-	} else {
-		ret.buf = ret.storage.asBytes()[d.rate-cap(d.buf) : d.rate]
-	}
-
 	return &ret
 }
 
@@ -82,43 +76,40 @@ func (d *state) permute() {
 	case spongeAbsorbing:
 		// If we're absorbing, we need to xor the input into the state
 		// before applying the permutation.
-		xorIn(d, d.buf)
-		d.buf = d.storage.asBytes()[:0]
+		xorIn(d, d.storage[:d.rate])
+		d.n = 0
 		keccakF1600(&d.a)
 	case spongeSqueezing:
 		// If we're squeezing, we need to apply the permutation before
 		// copying more output.
 		keccakF1600(&d.a)
-		d.buf = d.storage.asBytes()[:d.rate]
-		copyOut(d, d.buf)
+		d.i = 0
+		copyOut(d, d.storage[:d.rate])
 	}
 }
 
 // pads appends the domain separation bits in dsbyte, applies
 // the multi-bitrate 10..1 padding rule, and permutes the state.
-func (d *state) padAndPermute(dsbyte byte) {
-	if d.buf == nil {
-		d.buf = d.storage.asBytes()[:0]
-	}
+func (d *state) padAndPermute() {
 	// Pad with this instance's domain-separator bits. We know that there's
 	// at least one byte of space in d.buf because, if it were full,
 	// permute would have been called to empty it. dsbyte also contains the
 	// first one bit for the padding. See the comment in the state struct.
-	d.buf = append(d.buf, dsbyte)
-	zerosStart := len(d.buf)
-	d.buf = d.storage.asBytes()[:d.rate]
-	for i := zerosStart; i < d.rate; i++ {
-		d.buf[i] = 0
+	d.storage[d.n] = d.dsbyte
+	d.n++
+	for d.n < d.rate {
+		d.storage[d.n] = 0
+		d.n++
 	}
 	// This adds the final one bit for the padding. Because of the way that
 	// bits are numbered from the LSB upwards, the final bit is the MSB of
 	// the last byte.
-	d.buf[d.rate-1] ^= 0x80
+	d.storage[d.rate-1] ^= 0x80
 	// Apply the permutation
 	d.permute()
 	d.state = spongeSqueezing
-	d.buf = d.storage.asBytes()[:d.rate]
-	copyOut(d, d.buf)
+	d.n = d.rate
+	copyOut(d, d.storage[:d.rate])
 }
 
 // Write absorbs more data into the hash's state. It panics if any
@@ -127,28 +118,25 @@ func (d *state) Write(p []byte) (written int, err error) {
 	if d.state != spongeAbsorbing {
 		panic("sha3: Write after Read")
 	}
-	if d.buf == nil {
-		d.buf = d.storage.asBytes()[:0]
-	}
 	written = len(p)
 
 	for len(p) > 0 {
-		if len(d.buf) == 0 && len(p) >= d.rate {
+		if d.n == 0 && len(p) >= d.rate {
 			// The fast path; absorb a full "rate" bytes of input and apply the permutation.
 			xorIn(d, p[:d.rate])
 			p = p[d.rate:]
 			keccakF1600(&d.a)
 		} else {
 			// The slow path; buffer the input until we can fill the sponge, and then xor it in.
-			todo := d.rate - len(d.buf)
+			todo := d.rate - d.n
 			if todo > len(p) {
 				todo = len(p)
 			}
-			d.buf = append(d.buf, p[:todo]...)
+			d.n += copy(d.storage[d.n:], p[:todo])
 			p = p[todo:]
 
 			// If the sponge is full, apply the permutation.
-			if len(d.buf) == d.rate {
+			if d.n == d.rate {
 				d.permute()
 			}
 		}
@@ -161,19 +149,19 @@ func (d *state) Write(p []byte) (written int, err error) {
 func (d *state) Read(out []byte) (n int, err error) {
 	// If we're still absorbing, pad and apply the permutation.
 	if d.state == spongeAbsorbing {
-		d.padAndPermute(d.dsbyte)
+		d.padAndPermute()
 	}
 
 	n = len(out)
 
 	// Now, do the squeezing.
 	for len(out) > 0 {
-		n := copy(out, d.buf)
-		d.buf = d.buf[n:]
+		n := copy(out, d.storage[d.i:d.n])
+		d.i += n
 		out = out[n:]
 
 		// Apply the permutation if we've squeezed the sponge dry.
-		if len(d.buf) == 0 {
+		if d.i == d.rate {
 			d.permute()
 		}
 	}
diff --git a/vendor/golang.org/x/crypto/sha3/sha3_s390x.go b/vendor/golang.org/x/crypto/sha3/sha3_s390x.go
index b4fbbf8695c..00d8034ae62 100644
--- a/vendor/golang.org/x/crypto/sha3/sha3_s390x.go
+++ b/vendor/golang.org/x/crypto/sha3/sha3_s390x.go
@@ -248,56 +248,56 @@ func (s *asmState) Clone() ShakeHash {
 	return s.clone()
 }
 
-// new224Asm returns an assembly implementation of SHA3-224 if available,
-// otherwise it returns nil.
-func new224Asm() hash.Hash {
+// new224 returns an assembly implementation of SHA3-224 if available,
+// otherwise it returns a generic implementation.
+func new224() hash.Hash {
 	if cpu.S390X.HasSHA3 {
 		return newAsmState(sha3_224)
 	}
-	return nil
+	return new224Generic()
 }
 
-// new256Asm returns an assembly implementation of SHA3-256 if available,
-// otherwise it returns nil.
-func new256Asm() hash.Hash {
+// new256 returns an assembly implementation of SHA3-256 if available,
+// otherwise it returns a generic implementation.
+func new256() hash.Hash {
 	if cpu.S390X.HasSHA3 {
 		return newAsmState(sha3_256)
 	}
-	return nil
+	return new256Generic()
 }
 
-// new384Asm returns an assembly implementation of SHA3-384 if available,
-// otherwise it returns nil.
-func new384Asm() hash.Hash {
+// new384 returns an assembly implementation of SHA3-384 if available,
+// otherwise it returns a generic implementation.
+func new384() hash.Hash {
 	if cpu.S390X.HasSHA3 {
 		return newAsmState(sha3_384)
 	}
-	return nil
+	return new384Generic()
 }
 
-// new512Asm returns an assembly implementation of SHA3-512 if available,
-// otherwise it returns nil.
-func new512Asm() hash.Hash {
+// new512 returns an assembly implementation of SHA3-512 if available,
+// otherwise it returns a generic implementation.
+func new512() hash.Hash {
 	if cpu.S390X.HasSHA3 {
 		return newAsmState(sha3_512)
 	}
-	return nil
+	return new512Generic()
 }
 
-// newShake128Asm returns an assembly implementation of SHAKE-128 if available,
-// otherwise it returns nil.
-func newShake128Asm() ShakeHash {
+// newShake128 returns an assembly implementation of SHAKE-128 if available,
+// otherwise it returns a generic implementation.
+func newShake128() ShakeHash {
 	if cpu.S390X.HasSHA3 {
 		return newAsmState(shake_128)
 	}
-	return nil
+	return newShake128Generic()
 }
 
-// newShake256Asm returns an assembly implementation of SHAKE-256 if available,
-// otherwise it returns nil.
-func newShake256Asm() ShakeHash {
+// newShake256 returns an assembly implementation of SHAKE-256 if available,
+// otherwise it returns a generic implementation.
+func newShake256() ShakeHash {
 	if cpu.S390X.HasSHA3 {
 		return newAsmState(shake_256)
 	}
-	return nil
+	return newShake256Generic()
 }
diff --git a/vendor/golang.org/x/crypto/sha3/shake.go b/vendor/golang.org/x/crypto/sha3/shake.go
index bb69984027f..1ea9275b8b7 100644
--- a/vendor/golang.org/x/crypto/sha3/shake.go
+++ b/vendor/golang.org/x/crypto/sha3/shake.go
@@ -115,19 +115,21 @@ func (c *state) Clone() ShakeHash {
 // Its generic security strength is 128 bits against all attacks if at
 // least 32 bytes of its output are used.
 func NewShake128() ShakeHash {
-	if h := newShake128Asm(); h != nil {
-		return h
-	}
-	return &state{rate: rate128, outputLen: 32, dsbyte: dsbyteShake}
+	return newShake128()
 }
 
 // NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
 // Its generic security strength is 256 bits against all attacks if
 // at least 64 bytes of its output are used.
 func NewShake256() ShakeHash {
-	if h := newShake256Asm(); h != nil {
-		return h
-	}
+	return newShake256()
+}
+
+func newShake128Generic() *state {
+	return &state{rate: rate128, outputLen: 32, dsbyte: dsbyteShake}
+}
+
+func newShake256Generic() *state {
 	return &state{rate: rate256, outputLen: 64, dsbyte: dsbyteShake}
 }
 
diff --git a/vendor/golang.org/x/crypto/sha3/shake_generic.go b/vendor/golang.org/x/crypto/sha3/shake_generic.go
deleted file mode 100644
index 8d31cf5be2d..00000000000
--- a/vendor/golang.org/x/crypto/sha3/shake_generic.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2017 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 !gc || purego || !s390x
-
-package sha3
-
-// newShake128Asm returns an assembly implementation of SHAKE-128 if available,
-// otherwise it returns nil.
-func newShake128Asm() ShakeHash {
-	return nil
-}
-
-// newShake256Asm returns an assembly implementation of SHAKE-256 if available,
-// otherwise it returns nil.
-func newShake256Asm() ShakeHash {
-	return nil
-}
diff --git a/vendor/golang.org/x/crypto/sha3/shake_noasm.go b/vendor/golang.org/x/crypto/sha3/shake_noasm.go
new file mode 100644
index 00000000000..4276ba4ab2c
--- /dev/null
+++ b/vendor/golang.org/x/crypto/sha3/shake_noasm.go
@@ -0,0 +1,15 @@
+// Copyright 2023 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 !gc || purego || !s390x
+
+package sha3
+
+func newShake128() *state {
+	return newShake128Generic()
+}
+
+func newShake256() *state {
+	return newShake256Generic()
+}
diff --git a/vendor/golang.org/x/crypto/sha3/xor.go b/vendor/golang.org/x/crypto/sha3/xor.go
index 7337cca88ed..6ada5c9574e 100644
--- a/vendor/golang.org/x/crypto/sha3/xor.go
+++ b/vendor/golang.org/x/crypto/sha3/xor.go
@@ -2,22 +2,39 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build (!amd64 && !386 && !ppc64le) || purego
-
 package sha3
 
-// A storageBuf is an aligned array of maxRate bytes.
-type storageBuf [maxRate]byte
-
-func (b *storageBuf) asBytes() *[maxRate]byte {
-	return (*[maxRate]byte)(b)
-}
+import (
+	"crypto/subtle"
+	"encoding/binary"
+	"unsafe"
 
-var (
-	xorIn            = xorInGeneric
-	copyOut          = copyOutGeneric
-	xorInUnaligned   = xorInGeneric
-	copyOutUnaligned = copyOutGeneric
+	"golang.org/x/sys/cpu"
 )
 
-const xorImplementationUnaligned = "generic"
+// xorIn xors the bytes in buf into the state.
+func xorIn(d *state, buf []byte) {
+	if cpu.IsBigEndian {
+		for i := 0; len(buf) >= 8; i++ {
+			a := binary.LittleEndian.Uint64(buf)
+			d.a[i] ^= a
+			buf = buf[8:]
+		}
+	} else {
+		ab := (*[25 * 64 / 8]byte)(unsafe.Pointer(&d.a))
+		subtle.XORBytes(ab[:], ab[:], buf)
+	}
+}
+
+// copyOut copies uint64s to a byte buffer.
+func copyOut(d *state, b []byte) {
+	if cpu.IsBigEndian {
+		for i := 0; len(b) >= 8; i++ {
+			binary.LittleEndian.PutUint64(b, d.a[i])
+			b = b[8:]
+		}
+	} else {
+		ab := (*[25 * 64 / 8]byte)(unsafe.Pointer(&d.a))
+		copy(b, ab[:])
+	}
+}
diff --git a/vendor/golang.org/x/crypto/sha3/xor_generic.go b/vendor/golang.org/x/crypto/sha3/xor_generic.go
deleted file mode 100644
index 8d947711272..00000000000
--- a/vendor/golang.org/x/crypto/sha3/xor_generic.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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.
-
-package sha3
-
-import "encoding/binary"
-
-// xorInGeneric xors the bytes in buf into the state; it
-// makes no non-portable assumptions about memory layout
-// or alignment.
-func xorInGeneric(d *state, buf []byte) {
-	n := len(buf) / 8
-
-	for i := 0; i < n; i++ {
-		a := binary.LittleEndian.Uint64(buf)
-		d.a[i] ^= a
-		buf = buf[8:]
-	}
-}
-
-// copyOutGeneric copies uint64s to a byte buffer.
-func copyOutGeneric(d *state, b []byte) {
-	for i := 0; len(b) >= 8; i++ {
-		binary.LittleEndian.PutUint64(b, d.a[i])
-		b = b[8:]
-	}
-}
diff --git a/vendor/golang.org/x/crypto/sha3/xor_unaligned.go b/vendor/golang.org/x/crypto/sha3/xor_unaligned.go
deleted file mode 100644
index 870e2d16e07..00000000000
--- a/vendor/golang.org/x/crypto/sha3/xor_unaligned.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// 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 (amd64 || 386 || ppc64le) && !purego
-
-package sha3
-
-import "unsafe"
-
-// A storageBuf is an aligned array of maxRate bytes.
-type storageBuf [maxRate / 8]uint64
-
-func (b *storageBuf) asBytes() *[maxRate]byte {
-	return (*[maxRate]byte)(unsafe.Pointer(b))
-}
-
-// xorInUnaligned uses unaligned reads and writes to update d.a to contain d.a
-// XOR buf.
-func xorInUnaligned(d *state, buf []byte) {
-	n := len(buf)
-	bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8]
-	if n >= 72 {
-		d.a[0] ^= bw[0]
-		d.a[1] ^= bw[1]
-		d.a[2] ^= bw[2]
-		d.a[3] ^= bw[3]
-		d.a[4] ^= bw[4]
-		d.a[5] ^= bw[5]
-		d.a[6] ^= bw[6]
-		d.a[7] ^= bw[7]
-		d.a[8] ^= bw[8]
-	}
-	if n >= 104 {
-		d.a[9] ^= bw[9]
-		d.a[10] ^= bw[10]
-		d.a[11] ^= bw[11]
-		d.a[12] ^= bw[12]
-	}
-	if n >= 136 {
-		d.a[13] ^= bw[13]
-		d.a[14] ^= bw[14]
-		d.a[15] ^= bw[15]
-		d.a[16] ^= bw[16]
-	}
-	if n >= 144 {
-		d.a[17] ^= bw[17]
-	}
-	if n >= 168 {
-		d.a[18] ^= bw[18]
-		d.a[19] ^= bw[19]
-		d.a[20] ^= bw[20]
-	}
-}
-
-func copyOutUnaligned(d *state, buf []byte) {
-	ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0]))
-	copy(buf, ab[:])
-}
-
-var (
-	xorIn   = xorInUnaligned
-	copyOut = copyOutUnaligned
-)
-
-const xorImplementationUnaligned = "unaligned"
diff --git a/vendor/golang.org/x/crypto/ssh/keys.go b/vendor/golang.org/x/crypto/ssh/keys.go
index df4ebdada50..7967665f174 100644
--- a/vendor/golang.org/x/crypto/ssh/keys.go
+++ b/vendor/golang.org/x/crypto/ssh/keys.go
@@ -904,6 +904,10 @@ func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
 	return errors.New("ssh: signature did not verify")
 }
 
+func (k *skECDSAPublicKey) CryptoPublicKey() crypto.PublicKey {
+	return &k.PublicKey
+}
+
 type skEd25519PublicKey struct {
 	// application is a URL-like string, typically "ssh:" for SSH.
 	// see openssh/PROTOCOL.u2f for details.
@@ -1000,6 +1004,10 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
 	return nil
 }
 
+func (k *skEd25519PublicKey) CryptoPublicKey() crypto.PublicKey {
+	return k.PublicKey
+}
+
 // NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
 // *ecdsa.PrivateKey or any other crypto.Signer and returns a
 // corresponding Signer instance. ECDSA keys must use P-256, P-384 or
diff --git a/vendor/golang.org/x/crypto/ssh/server.go b/vendor/golang.org/x/crypto/ssh/server.go
index e2ae4f891bb..3ca9e89e22e 100644
--- a/vendor/golang.org/x/crypto/ssh/server.go
+++ b/vendor/golang.org/x/crypto/ssh/server.go
@@ -462,6 +462,24 @@ func (p *PartialSuccessError) Error() string {
 // It is returned in ServerAuthError.Errors from NewServerConn.
 var ErrNoAuth = errors.New("ssh: no auth passed yet")
 
+// BannerError is an error that can be returned by authentication handlers in
+// ServerConfig to send a banner message to the client.
+type BannerError struct {
+	Err     error
+	Message string
+}
+
+func (b *BannerError) Unwrap() error {
+	return b.Err
+}
+
+func (b *BannerError) Error() string {
+	if b.Err == nil {
+		return b.Message
+	}
+	return b.Err.Error()
+}
+
 func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
 	sessionID := s.transport.getSessionID()
 	var cache pubKeyCache
@@ -734,6 +752,18 @@ userAuthLoop:
 			config.AuthLogCallback(s, userAuthReq.Method, authErr)
 		}
 
+		var bannerErr *BannerError
+		if errors.As(authErr, &bannerErr) {
+			if bannerErr.Message != "" {
+				bannerMsg := &userAuthBannerMsg{
+					Message: bannerErr.Message,
+				}
+				if err := s.transport.writePacket(Marshal(bannerMsg)); err != nil {
+					return nil, err
+				}
+			}
+		}
+
 		if authErr == nil {
 			break userAuthLoop
 		}
diff --git a/vendor/golang.org/x/net/http2/http2.go b/vendor/golang.org/x/net/http2/http2.go
index 6f2df281872..003e649f30c 100644
--- a/vendor/golang.org/x/net/http2/http2.go
+++ b/vendor/golang.org/x/net/http2/http2.go
@@ -17,6 +17,7 @@ package http2 // import "golang.org/x/net/http2"
 
 import (
 	"bufio"
+	"context"
 	"crypto/tls"
 	"fmt"
 	"io"
@@ -26,6 +27,7 @@ import (
 	"strconv"
 	"strings"
 	"sync"
+	"time"
 
 	"golang.org/x/net/http/httpguts"
 )
@@ -210,12 +212,6 @@ type stringWriter interface {
 	WriteString(s string) (n int, err error)
 }
 
-// A gate lets two goroutines coordinate their activities.
-type gate chan struct{}
-
-func (g gate) Done() { g <- struct{}{} }
-func (g gate) Wait() { <-g }
-
 // A closeWaiter is like a sync.WaitGroup but only goes 1 to 0 (open to closed).
 type closeWaiter chan struct{}
 
@@ -383,3 +379,14 @@ func validPseudoPath(v string) bool {
 // makes that struct also non-comparable, and generally doesn't add
 // any size (as long as it's first).
 type incomparable [0]func()
+
+// synctestGroupInterface is the methods of synctestGroup used by Server and Transport.
+// It's defined as an interface here to let us keep synctestGroup entirely test-only
+// and not a part of non-test builds.
+type synctestGroupInterface interface {
+	Join()
+	Now() time.Time
+	NewTimer(d time.Duration) timer
+	AfterFunc(d time.Duration, f func()) timer
+	ContextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc)
+}
diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go
index c5d08108137..6c349f3ec64 100644
--- a/vendor/golang.org/x/net/http2/server.go
+++ b/vendor/golang.org/x/net/http2/server.go
@@ -154,6 +154,39 @@ type Server struct {
 	// so that we don't embed a Mutex in this struct, which will make the
 	// struct non-copyable, which might break some callers.
 	state *serverInternalState
+
+	// Synchronization group used for testing.
+	// Outside of tests, this is nil.
+	group synctestGroupInterface
+}
+
+func (s *Server) markNewGoroutine() {
+	if s.group != nil {
+		s.group.Join()
+	}
+}
+
+func (s *Server) now() time.Time {
+	if s.group != nil {
+		return s.group.Now()
+	}
+	return time.Now()
+}
+
+// newTimer creates a new time.Timer, or a synthetic timer in tests.
+func (s *Server) newTimer(d time.Duration) timer {
+	if s.group != nil {
+		return s.group.NewTimer(d)
+	}
+	return timeTimer{time.NewTimer(d)}
+}
+
+// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
+func (s *Server) afterFunc(d time.Duration, f func()) timer {
+	if s.group != nil {
+		return s.group.AfterFunc(d, f)
+	}
+	return timeTimer{time.AfterFunc(d, f)}
 }
 
 func (s *Server) initialConnRecvWindowSize() int32 {
@@ -400,6 +433,10 @@ func (o *ServeConnOpts) handler() http.Handler {
 //
 // The opts parameter is optional. If nil, default values are used.
 func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
+	s.serveConn(c, opts, nil)
+}
+
+func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverConn)) {
 	baseCtx, cancel := serverConnBaseContext(c, opts)
 	defer cancel()
 
@@ -426,6 +463,9 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
 		pushEnabled:                 true,
 		sawClientPreface:            opts.SawClientPreface,
 	}
+	if newf != nil {
+		newf(sc)
+	}
 
 	s.state.registerConn(sc)
 	defer s.state.unregisterConn(sc)
@@ -599,8 +639,8 @@ type serverConn struct {
 	inFrameScheduleLoop         bool              // whether we're in the scheduleFrameWrite loop
 	needToSendGoAway            bool              // we need to schedule a GOAWAY frame write
 	goAwayCode                  ErrCode
-	shutdownTimer               *time.Timer // nil until used
-	idleTimer                   *time.Timer // nil if unused
+	shutdownTimer               timer // nil until used
+	idleTimer                   timer // nil if unused
 
 	// Owned by the writeFrameAsync goroutine:
 	headerWriteBuf bytes.Buffer
@@ -649,12 +689,12 @@ type stream struct {
 	flow             outflow // limits writing from Handler to client
 	inflow           inflow  // what the client is allowed to POST/etc to us
 	state            streamState
-	resetQueued      bool        // RST_STREAM queued for write; set by sc.resetStream
-	gotTrailerHeader bool        // HEADER frame for trailers was seen
-	wroteHeaders     bool        // whether we wrote headers (not status 100)
-	readDeadline     *time.Timer // nil if unused
-	writeDeadline    *time.Timer // nil if unused
-	closeErr         error       // set before cw is closed
+	resetQueued      bool  // RST_STREAM queued for write; set by sc.resetStream
+	gotTrailerHeader bool  // HEADER frame for trailers was seen
+	wroteHeaders     bool  // whether we wrote headers (not status 100)
+	readDeadline     timer // nil if unused
+	writeDeadline    timer // nil if unused
+	closeErr         error // set before cw is closed
 
 	trailer    http.Header // accumulated trailers
 	reqTrailer http.Header // handler's Request.Trailer
@@ -811,8 +851,9 @@ type readFrameResult struct {
 // consumer is done with the frame.
 // It's run on its own goroutine.
 func (sc *serverConn) readFrames() {
-	gate := make(gate)
-	gateDone := gate.Done
+	sc.srv.markNewGoroutine()
+	gate := make(chan struct{})
+	gateDone := func() { gate <- struct{}{} }
 	for {
 		f, err := sc.framer.ReadFrame()
 		select {
@@ -843,6 +884,7 @@ type frameWriteResult struct {
 // At most one goroutine can be running writeFrameAsync at a time per
 // serverConn.
 func (sc *serverConn) writeFrameAsync(wr FrameWriteRequest, wd *writeData) {
+	sc.srv.markNewGoroutine()
 	var err error
 	if wd == nil {
 		err = wr.write.writeFrame(sc)
@@ -922,13 +964,13 @@ func (sc *serverConn) serve() {
 	sc.setConnState(http.StateIdle)
 
 	if sc.srv.IdleTimeout > 0 {
-		sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
+		sc.idleTimer = sc.srv.afterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
 		defer sc.idleTimer.Stop()
 	}
 
 	go sc.readFrames() // closed by defer sc.conn.Close above
 
-	settingsTimer := time.AfterFunc(firstSettingsTimeout, sc.onSettingsTimer)
+	settingsTimer := sc.srv.afterFunc(firstSettingsTimeout, sc.onSettingsTimer)
 	defer settingsTimer.Stop()
 
 	loopNum := 0
@@ -1057,10 +1099,10 @@ func (sc *serverConn) readPreface() error {
 			errc <- nil
 		}
 	}()
-	timer := time.NewTimer(prefaceTimeout) // TODO: configurable on *Server?
+	timer := sc.srv.newTimer(prefaceTimeout) // TODO: configurable on *Server?
 	defer timer.Stop()
 	select {
-	case <-timer.C:
+	case <-timer.C():
 		return errPrefaceTimeout
 	case err := <-errc:
 		if err == nil {
@@ -1425,7 +1467,7 @@ func (sc *serverConn) goAway(code ErrCode) {
 
 func (sc *serverConn) shutDownIn(d time.Duration) {
 	sc.serveG.check()
-	sc.shutdownTimer = time.AfterFunc(d, sc.onShutdownTimer)
+	sc.shutdownTimer = sc.srv.afterFunc(d, sc.onShutdownTimer)
 }
 
 func (sc *serverConn) resetStream(se StreamError) {
@@ -1639,7 +1681,7 @@ func (sc *serverConn) closeStream(st *stream, err error) {
 	delete(sc.streams, st.id)
 	if len(sc.streams) == 0 {
 		sc.setConnState(http.StateIdle)
-		if sc.srv.IdleTimeout > 0 {
+		if sc.srv.IdleTimeout > 0 && sc.idleTimer != nil {
 			sc.idleTimer.Reset(sc.srv.IdleTimeout)
 		}
 		if h1ServerKeepAlivesDisabled(sc.hs) {
@@ -1661,6 +1703,7 @@ func (sc *serverConn) closeStream(st *stream, err error) {
 		}
 	}
 	st.closeErr = err
+	st.cancelCtx()
 	st.cw.Close() // signals Handler's CloseNotifier, unblocks writes, etc
 	sc.writeSched.CloseStream(st.id)
 }
@@ -2021,7 +2064,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
 	// (in Go 1.8), though. That's a more sane option anyway.
 	if sc.hs.ReadTimeout > 0 {
 		sc.conn.SetReadDeadline(time.Time{})
-		st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
+		st.readDeadline = sc.srv.afterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
 	}
 
 	return sc.scheduleHandler(id, rw, req, handler)
@@ -2119,7 +2162,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
 	st.flow.add(sc.initialStreamSendWindowSize)
 	st.inflow.init(sc.srv.initialStreamRecvWindowSize())
 	if sc.hs.WriteTimeout > 0 {
-		st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
+		st.writeDeadline = sc.srv.afterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
 	}
 
 	sc.streams[id] = st
@@ -2343,6 +2386,7 @@ func (sc *serverConn) handlerDone() {
 
 // Run on its own goroutine.
 func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) {
+	sc.srv.markNewGoroutine()
 	defer sc.sendServeMsg(handlerDoneMsg)
 	didPanic := true
 	defer func() {
@@ -2639,7 +2683,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
 		var date string
 		if _, ok := rws.snapHeader["Date"]; !ok {
 			// TODO(bradfitz): be faster here, like net/http? measure.
-			date = time.Now().UTC().Format(http.TimeFormat)
+			date = rws.conn.srv.now().UTC().Format(http.TimeFormat)
 		}
 
 		for _, v := range rws.snapHeader["Trailer"] {
@@ -2761,7 +2805,7 @@ func (rws *responseWriterState) promoteUndeclaredTrailers() {
 
 func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
 	st := w.rws.stream
-	if !deadline.IsZero() && deadline.Before(time.Now()) {
+	if !deadline.IsZero() && deadline.Before(w.rws.conn.srv.now()) {
 		// If we're setting a deadline in the past, reset the stream immediately
 		// so writes after SetWriteDeadline returns will fail.
 		st.onReadTimeout()
@@ -2777,9 +2821,9 @@ func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
 		if deadline.IsZero() {
 			st.readDeadline = nil
 		} else if st.readDeadline == nil {
-			st.readDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onReadTimeout)
+			st.readDeadline = sc.srv.afterFunc(deadline.Sub(sc.srv.now()), st.onReadTimeout)
 		} else {
-			st.readDeadline.Reset(deadline.Sub(time.Now()))
+			st.readDeadline.Reset(deadline.Sub(sc.srv.now()))
 		}
 	})
 	return nil
@@ -2787,7 +2831,7 @@ func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
 
 func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
 	st := w.rws.stream
-	if !deadline.IsZero() && deadline.Before(time.Now()) {
+	if !deadline.IsZero() && deadline.Before(w.rws.conn.srv.now()) {
 		// If we're setting a deadline in the past, reset the stream immediately
 		// so writes after SetWriteDeadline returns will fail.
 		st.onWriteTimeout()
@@ -2803,9 +2847,9 @@ func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
 		if deadline.IsZero() {
 			st.writeDeadline = nil
 		} else if st.writeDeadline == nil {
-			st.writeDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onWriteTimeout)
+			st.writeDeadline = sc.srv.afterFunc(deadline.Sub(sc.srv.now()), st.onWriteTimeout)
 		} else {
-			st.writeDeadline.Reset(deadline.Sub(time.Now()))
+			st.writeDeadline.Reset(deadline.Sub(sc.srv.now()))
 		}
 	})
 	return nil
diff --git a/vendor/golang.org/x/net/http2/timer.go b/vendor/golang.org/x/net/http2/timer.go
new file mode 100644
index 00000000000..0b1c17b8129
--- /dev/null
+++ b/vendor/golang.org/x/net/http2/timer.go
@@ -0,0 +1,20 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package http2
+
+import "time"
+
+// A timer is a time.Timer, as an interface which can be replaced in tests.
+type timer = interface {
+	C() <-chan time.Time
+	Reset(d time.Duration) bool
+	Stop() bool
+}
+
+// timeTimer adapts a time.Timer to the timer interface.
+type timeTimer struct {
+	*time.Timer
+}
+
+func (t timeTimer) C() <-chan time.Time { return t.Timer.C }
diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go
index 2fa49490c9d..98a49c6b6ee 100644
--- a/vendor/golang.org/x/net/http2/transport.go
+++ b/vendor/golang.org/x/net/http2/transport.go
@@ -185,7 +185,45 @@ type Transport struct {
 	connPoolOnce  sync.Once
 	connPoolOrDef ClientConnPool // non-nil version of ConnPool
 
-	syncHooks *testSyncHooks
+	*transportTestHooks
+}
+
+// Hook points used for testing.
+// Outside of tests, t.transportTestHooks is nil and these all have minimal implementations.
+// Inside tests, see the testSyncHooks function docs.
+
+type transportTestHooks struct {
+	newclientconn func(*ClientConn)
+	group         synctestGroupInterface
+}
+
+func (t *Transport) markNewGoroutine() {
+	if t != nil && t.transportTestHooks != nil {
+		t.transportTestHooks.group.Join()
+	}
+}
+
+// newTimer creates a new time.Timer, or a synthetic timer in tests.
+func (t *Transport) newTimer(d time.Duration) timer {
+	if t.transportTestHooks != nil {
+		return t.transportTestHooks.group.NewTimer(d)
+	}
+	return timeTimer{time.NewTimer(d)}
+}
+
+// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
+func (t *Transport) afterFunc(d time.Duration, f func()) timer {
+	if t.transportTestHooks != nil {
+		return t.transportTestHooks.group.AfterFunc(d, f)
+	}
+	return timeTimer{time.AfterFunc(d, f)}
+}
+
+func (t *Transport) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
+	if t.transportTestHooks != nil {
+		return t.transportTestHooks.group.ContextWithTimeout(ctx, d)
+	}
+	return context.WithTimeout(ctx, d)
 }
 
 func (t *Transport) maxHeaderListSize() uint32 {
@@ -352,60 +390,6 @@ type ClientConn struct {
 	werr error        // first write error that has occurred
 	hbuf bytes.Buffer // HPACK encoder writes into this
 	henc *hpack.Encoder
-
-	syncHooks *testSyncHooks // can be nil
-}
-
-// Hook points used for testing.
-// Outside of tests, cc.syncHooks is nil and these all have minimal implementations.
-// Inside tests, see the testSyncHooks function docs.
-
-// goRun starts a new goroutine.
-func (cc *ClientConn) goRun(f func()) {
-	if cc.syncHooks != nil {
-		cc.syncHooks.goRun(f)
-		return
-	}
-	go f()
-}
-
-// condBroadcast is cc.cond.Broadcast.
-func (cc *ClientConn) condBroadcast() {
-	if cc.syncHooks != nil {
-		cc.syncHooks.condBroadcast(cc.cond)
-	}
-	cc.cond.Broadcast()
-}
-
-// condWait is cc.cond.Wait.
-func (cc *ClientConn) condWait() {
-	if cc.syncHooks != nil {
-		cc.syncHooks.condWait(cc.cond)
-	}
-	cc.cond.Wait()
-}
-
-// newTimer creates a new time.Timer, or a synthetic timer in tests.
-func (cc *ClientConn) newTimer(d time.Duration) timer {
-	if cc.syncHooks != nil {
-		return cc.syncHooks.newTimer(d)
-	}
-	return newTimeTimer(d)
-}
-
-// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
-func (cc *ClientConn) afterFunc(d time.Duration, f func()) timer {
-	if cc.syncHooks != nil {
-		return cc.syncHooks.afterFunc(d, f)
-	}
-	return newTimeAfterFunc(d, f)
-}
-
-func (cc *ClientConn) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
-	if cc.syncHooks != nil {
-		return cc.syncHooks.contextWithTimeout(ctx, d)
-	}
-	return context.WithTimeout(ctx, d)
 }
 
 // clientStream is the state for a single HTTP/2 stream. One of these
@@ -487,7 +471,7 @@ func (cs *clientStream) abortStreamLocked(err error) {
 	// TODO(dneil): Clean up tests where cs.cc.cond is nil.
 	if cs.cc.cond != nil {
 		// Wake up writeRequestBody if it is waiting on flow control.
-		cs.cc.condBroadcast()
+		cs.cc.cond.Broadcast()
 	}
 }
 
@@ -497,7 +481,7 @@ func (cs *clientStream) abortRequestBodyWrite() {
 	defer cc.mu.Unlock()
 	if cs.reqBody != nil && cs.reqBodyClosed == nil {
 		cs.closeReqBodyLocked()
-		cc.condBroadcast()
+		cc.cond.Broadcast()
 	}
 }
 
@@ -507,10 +491,11 @@ func (cs *clientStream) closeReqBodyLocked() {
 	}
 	cs.reqBodyClosed = make(chan struct{})
 	reqBodyClosed := cs.reqBodyClosed
-	cs.cc.goRun(func() {
+	go func() {
+		cs.cc.t.markNewGoroutine()
 		cs.reqBody.Close()
 		close(reqBodyClosed)
-	})
+	}()
 }
 
 type stickyErrWriter struct {
@@ -626,21 +611,7 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
 				backoff := float64(uint(1) << (uint(retry) - 1))
 				backoff += backoff * (0.1 * mathrand.Float64())
 				d := time.Second * time.Duration(backoff)
-				var tm timer
-				if t.syncHooks != nil {
-					tm = t.syncHooks.newTimer(d)
-					t.syncHooks.blockUntil(func() bool {
-						select {
-						case <-tm.C():
-						case <-req.Context().Done():
-						default:
-							return false
-						}
-						return true
-					})
-				} else {
-					tm = newTimeTimer(d)
-				}
+				tm := t.newTimer(d)
 				select {
 				case <-tm.C():
 					t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
@@ -725,8 +696,8 @@ func canRetryError(err error) bool {
 }
 
 func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
-	if t.syncHooks != nil {
-		return t.newClientConn(nil, singleUse, t.syncHooks)
+	if t.transportTestHooks != nil {
+		return t.newClientConn(nil, singleUse)
 	}
 	host, _, err := net.SplitHostPort(addr)
 	if err != nil {
@@ -736,7 +707,7 @@ func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse b
 	if err != nil {
 		return nil, err
 	}
-	return t.newClientConn(tconn, singleUse, nil)
+	return t.newClientConn(tconn, singleUse)
 }
 
 func (t *Transport) newTLSConfig(host string) *tls.Config {
@@ -802,10 +773,10 @@ func (t *Transport) maxEncoderHeaderTableSize() uint32 {
 }
 
 func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
-	return t.newClientConn(c, t.disableKeepAlives(), nil)
+	return t.newClientConn(c, t.disableKeepAlives())
 }
 
-func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHooks) (*ClientConn, error) {
+func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
 	cc := &ClientConn{
 		t:                     t,
 		tconn:                 c,
@@ -820,16 +791,12 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHoo
 		wantSettingsAck:       true,
 		pings:                 make(map[[8]byte]chan struct{}),
 		reqHeaderMu:           make(chan struct{}, 1),
-		syncHooks:             hooks,
 	}
-	if hooks != nil {
-		hooks.newclientconn(cc)
+	if t.transportTestHooks != nil {
+		t.markNewGoroutine()
+		t.transportTestHooks.newclientconn(cc)
 		c = cc.tconn
 	}
-	if d := t.idleConnTimeout(); d != 0 {
-		cc.idleTimeout = d
-		cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
-	}
 	if VerboseLogs {
 		t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
 	}
@@ -893,7 +860,13 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHoo
 		return nil, cc.werr
 	}
 
-	cc.goRun(cc.readLoop)
+	// Start the idle timer after the connection is fully initialized.
+	if d := t.idleConnTimeout(); d != 0 {
+		cc.idleTimeout = d
+		cc.idleTimer = t.afterFunc(d, cc.onIdleTimeout)
+	}
+
+	go cc.readLoop()
 	return cc, nil
 }
 
@@ -901,7 +874,7 @@ func (cc *ClientConn) healthCheck() {
 	pingTimeout := cc.t.pingTimeout()
 	// We don't need to periodically ping in the health check, because the readLoop of ClientConn will
 	// trigger the healthCheck again if there is no frame received.
-	ctx, cancel := cc.contextWithTimeout(context.Background(), pingTimeout)
+	ctx, cancel := cc.t.contextWithTimeout(context.Background(), pingTimeout)
 	defer cancel()
 	cc.vlogf("http2: Transport sending health check")
 	err := cc.Ping(ctx)
@@ -1144,7 +1117,8 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
 	// Wait for all in-flight streams to complete or connection to close
 	done := make(chan struct{})
 	cancelled := false // guarded by cc.mu
-	cc.goRun(func() {
+	go func() {
+		cc.t.markNewGoroutine()
 		cc.mu.Lock()
 		defer cc.mu.Unlock()
 		for {
@@ -1156,9 +1130,9 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
 			if cancelled {
 				break
 			}
-			cc.condWait()
+			cc.cond.Wait()
 		}
-	})
+	}()
 	shutdownEnterWaitStateHook()
 	select {
 	case <-done:
@@ -1168,7 +1142,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
 		cc.mu.Lock()
 		// Free the goroutine above
 		cancelled = true
-		cc.condBroadcast()
+		cc.cond.Broadcast()
 		cc.mu.Unlock()
 		return ctx.Err()
 	}
@@ -1206,7 +1180,7 @@ func (cc *ClientConn) closeForError(err error) {
 	for _, cs := range cc.streams {
 		cs.abortStreamLocked(err)
 	}
-	cc.condBroadcast()
+	cc.cond.Broadcast()
 	cc.mu.Unlock()
 	cc.closeConn()
 }
@@ -1321,23 +1295,30 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream))
 		respHeaderRecv:       make(chan struct{}),
 		donec:                make(chan struct{}),
 	}
-	cc.goRun(func() {
-		cs.doRequest(req)
-	})
+
+	// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
+	if !cc.t.disableCompression() &&
+		req.Header.Get("Accept-Encoding") == "" &&
+		req.Header.Get("Range") == "" &&
+		!cs.isHead {
+		// Request gzip only, not deflate. Deflate is ambiguous and
+		// not as universally supported anyway.
+		// See: https://zlib.net/zlib_faq.html#faq39
+		//
+		// Note that we don't request this for HEAD requests,
+		// due to a bug in nginx:
+		//   http://trac.nginx.org/nginx/ticket/358
+		//   https://golang.org/issue/5522
+		//
+		// We don't request gzip if the request is for a range, since
+		// auto-decoding a portion of a gzipped document will just fail
+		// anyway. See https://golang.org/issue/8923
+		cs.requestedGzip = true
+	}
+
+	go cs.doRequest(req, streamf)
 
 	waitDone := func() error {
-		if cc.syncHooks != nil {
-			cc.syncHooks.blockUntil(func() bool {
-				select {
-				case <-cs.donec:
-				case <-ctx.Done():
-				case <-cs.reqCancel:
-				default:
-					return false
-				}
-				return true
-			})
-		}
 		select {
 		case <-cs.donec:
 			return nil
@@ -1398,24 +1379,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream))
 		return err
 	}
 
-	if streamf != nil {
-		streamf(cs)
-	}
-
 	for {
-		if cc.syncHooks != nil {
-			cc.syncHooks.blockUntil(func() bool {
-				select {
-				case <-cs.respHeaderRecv:
-				case <-cs.abort:
-				case <-ctx.Done():
-				case <-cs.reqCancel:
-				default:
-					return false
-				}
-				return true
-			})
-		}
 		select {
 		case <-cs.respHeaderRecv:
 			return handleResponseHeaders()
@@ -1445,8 +1409,9 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream))
 // doRequest runs for the duration of the request lifetime.
 //
 // It sends the request and performs post-request cleanup (closing Request.Body, etc.).
-func (cs *clientStream) doRequest(req *http.Request) {
-	err := cs.writeRequest(req)
+func (cs *clientStream) doRequest(req *http.Request, streamf func(*clientStream)) {
+	cs.cc.t.markNewGoroutine()
+	err := cs.writeRequest(req, streamf)
 	cs.cleanupWriteRequest(err)
 }
 
@@ -1457,7 +1422,7 @@ func (cs *clientStream) doRequest(req *http.Request) {
 //
 // It returns non-nil if the request ends otherwise.
 // If the returned error is StreamError, the error Code may be used in resetting the stream.
-func (cs *clientStream) writeRequest(req *http.Request) (err error) {
+func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStream)) (err error) {
 	cc := cs.cc
 	ctx := cs.ctx
 
@@ -1471,21 +1436,6 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
 	if cc.reqHeaderMu == nil {
 		panic("RoundTrip on uninitialized ClientConn") // for tests
 	}
-	var newStreamHook func(*clientStream)
-	if cc.syncHooks != nil {
-		newStreamHook = cc.syncHooks.newstream
-		cc.syncHooks.blockUntil(func() bool {
-			select {
-			case cc.reqHeaderMu <- struct{}{}:
-				<-cc.reqHeaderMu
-			case <-cs.reqCancel:
-			case <-ctx.Done():
-			default:
-				return false
-			}
-			return true
-		})
-	}
 	select {
 	case cc.reqHeaderMu <- struct{}{}:
 	case <-cs.reqCancel:
@@ -1510,28 +1460,8 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
 	}
 	cc.mu.Unlock()
 
-	if newStreamHook != nil {
-		newStreamHook(cs)
-	}
-
-	// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
-	if !cc.t.disableCompression() &&
-		req.Header.Get("Accept-Encoding") == "" &&
-		req.Header.Get("Range") == "" &&
-		!cs.isHead {
-		// Request gzip only, not deflate. Deflate is ambiguous and
-		// not as universally supported anyway.
-		// See: https://zlib.net/zlib_faq.html#faq39
-		//
-		// Note that we don't request this for HEAD requests,
-		// due to a bug in nginx:
-		//   http://trac.nginx.org/nginx/ticket/358
-		//   https://golang.org/issue/5522
-		//
-		// We don't request gzip if the request is for a range, since
-		// auto-decoding a portion of a gzipped document will just fail
-		// anyway. See https://golang.org/issue/8923
-		cs.requestedGzip = true
+	if streamf != nil {
+		streamf(cs)
 	}
 
 	continueTimeout := cc.t.expectContinueTimeout()
@@ -1594,7 +1524,7 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
 	var respHeaderTimer <-chan time.Time
 	var respHeaderRecv chan struct{}
 	if d := cc.responseHeaderTimeout(); d != 0 {
-		timer := cc.newTimer(d)
+		timer := cc.t.newTimer(d)
 		defer timer.Stop()
 		respHeaderTimer = timer.C()
 		respHeaderRecv = cs.respHeaderRecv
@@ -1603,21 +1533,6 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
 	// or until the request is aborted (via context, error, or otherwise),
 	// whichever comes first.
 	for {
-		if cc.syncHooks != nil {
-			cc.syncHooks.blockUntil(func() bool {
-				select {
-				case <-cs.peerClosed:
-				case <-respHeaderTimer:
-				case <-respHeaderRecv:
-				case <-cs.abort:
-				case <-ctx.Done():
-				case <-cs.reqCancel:
-				default:
-					return false
-				}
-				return true
-			})
-		}
 		select {
 		case <-cs.peerClosed:
 			return nil
@@ -1766,7 +1681,7 @@ func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error {
 			return nil
 		}
 		cc.pendingRequests++
-		cc.condWait()
+		cc.cond.Wait()
 		cc.pendingRequests--
 		select {
 		case <-cs.abort:
@@ -2028,7 +1943,7 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
 			cs.flow.take(take)
 			return take, nil
 		}
-		cc.condWait()
+		cc.cond.Wait()
 	}
 }
 
@@ -2311,7 +2226,7 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
 	}
 	// Wake up writeRequestBody via clientStream.awaitFlowControl and
 	// wake up RoundTrip if there is a pending request.
-	cc.condBroadcast()
+	cc.cond.Broadcast()
 
 	closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
 	if closeOnIdle && cc.streamsReserved == 0 && len(cc.streams) == 0 {
@@ -2333,6 +2248,7 @@ type clientConnReadLoop struct {
 
 // readLoop runs in its own goroutine and reads and dispatches frames.
 func (cc *ClientConn) readLoop() {
+	cc.t.markNewGoroutine()
 	rl := &clientConnReadLoop{cc: cc}
 	defer rl.cleanup()
 	cc.readerErr = rl.run()
@@ -2399,7 +2315,7 @@ func (rl *clientConnReadLoop) cleanup() {
 			cs.abortStreamLocked(err)
 		}
 	}
-	cc.condBroadcast()
+	cc.cond.Broadcast()
 	cc.mu.Unlock()
 }
 
@@ -2436,7 +2352,7 @@ func (rl *clientConnReadLoop) run() error {
 	readIdleTimeout := cc.t.ReadIdleTimeout
 	var t timer
 	if readIdleTimeout != 0 {
-		t = cc.afterFunc(readIdleTimeout, cc.healthCheck)
+		t = cc.t.afterFunc(readIdleTimeout, cc.healthCheck)
 	}
 	for {
 		f, err := cc.fr.ReadFrame()
@@ -3034,7 +2950,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
 			for _, cs := range cc.streams {
 				cs.flow.add(delta)
 			}
-			cc.condBroadcast()
+			cc.cond.Broadcast()
 
 			cc.initialWindowSize = s.Val
 		case SettingHeaderTableSize:
@@ -3089,7 +3005,7 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
 
 		return ConnectionError(ErrCodeFlowControl)
 	}
-	cc.condBroadcast()
+	cc.cond.Broadcast()
 	return nil
 }
 
@@ -3133,7 +3049,8 @@ func (cc *ClientConn) Ping(ctx context.Context) error {
 	}
 	var pingError error
 	errc := make(chan struct{})
-	cc.goRun(func() {
+	go func() {
+		cc.t.markNewGoroutine()
 		cc.wmu.Lock()
 		defer cc.wmu.Unlock()
 		if pingError = cc.fr.WritePing(false, p); pingError != nil {
@@ -3144,20 +3061,7 @@ func (cc *ClientConn) Ping(ctx context.Context) error {
 			close(errc)
 			return
 		}
-	})
-	if cc.syncHooks != nil {
-		cc.syncHooks.blockUntil(func() bool {
-			select {
-			case <-c:
-			case <-errc:
-			case <-ctx.Done():
-			case <-cc.readerDone:
-			default:
-				return false
-			}
-			return true
-		})
-	}
+	}()
 	select {
 	case <-c:
 		return nil
diff --git a/vendor/golang.org/x/net/http2/writesched_priority.go b/vendor/golang.org/x/net/http2/writesched_priority.go
index 0a242c669e2..f6783339d11 100644
--- a/vendor/golang.org/x/net/http2/writesched_priority.go
+++ b/vendor/golang.org/x/net/http2/writesched_priority.go
@@ -443,8 +443,8 @@ func (ws *priorityWriteScheduler) addClosedOrIdleNode(list *[]*priorityNode, max
 }
 
 func (ws *priorityWriteScheduler) removeNode(n *priorityNode) {
-	for k := n.kids; k != nil; k = k.next {
-		k.setParent(n.parent)
+	for n.kids != nil {
+		n.kids.setParent(n.parent)
 	}
 	n.setParent(nil)
 	delete(ws.nodes, n.id)
diff --git a/vendor/golang.org/x/net/proxy/per_host.go b/vendor/golang.org/x/net/proxy/per_host.go
index 573fe79e86e..d7d4b8b6e35 100644
--- a/vendor/golang.org/x/net/proxy/per_host.go
+++ b/vendor/golang.org/x/net/proxy/per_host.go
@@ -137,9 +137,7 @@ func (p *PerHost) AddNetwork(net *net.IPNet) {
 // AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
 // "example.com" matches "example.com" and all of its subdomains.
 func (p *PerHost) AddZone(zone string) {
-	if strings.HasSuffix(zone, ".") {
-		zone = zone[:len(zone)-1]
-	}
+	zone = strings.TrimSuffix(zone, ".")
 	if !strings.HasPrefix(zone, ".") {
 		zone = "." + zone
 	}
@@ -148,8 +146,6 @@ func (p *PerHost) AddZone(zone string) {
 
 // AddHost specifies a host name that will use the bypass proxy.
 func (p *PerHost) AddHost(host string) {
-	if strings.HasSuffix(host, ".") {
-		host = host[:len(host)-1]
-	}
+	host = strings.TrimSuffix(host, ".")
 	p.bypassHosts = append(p.bypassHosts, host)
 }
diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh
index fdcaa974d23..4ed2e488b61 100644
--- a/vendor/golang.org/x/sys/unix/mkerrors.sh
+++ b/vendor/golang.org/x/sys/unix/mkerrors.sh
@@ -263,6 +263,7 @@ struct ltchars {
 #include <linux/sched.h>
 #include <linux/seccomp.h>
 #include <linux/serial.h>
+#include <linux/sock_diag.h>
 #include <linux/sockios.h>
 #include <linux/taskstats.h>
 #include <linux/tipc.h>
@@ -549,6 +550,7 @@ ccflags="$@"
 		$2 !~ "NLA_TYPE_MASK" &&
 		$2 !~ /^RTC_VL_(ACCURACY|BACKUP|DATA)/ &&
 		$2 ~ /^(NETLINK|NLM|NLMSG|NLA|IFA|IFAN|RT|RTC|RTCF|RTN|RTPROT|RTNH|ARPHRD|ETH_P|NETNSA)_/ ||
+		$2 ~ /^SOCK_|SK_DIAG_|SKNLGRP_$/ ||
 		$2 ~ /^FIORDCHK$/ ||
 		$2 ~ /^SIOC/ ||
 		$2 ~ /^TIOC/ ||
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go
index 93a38a97d9c..877a62b479a 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go
@@ -502,6 +502,7 @@ const (
 	BPF_IMM                                     = 0x0
 	BPF_IND                                     = 0x40
 	BPF_JA                                      = 0x0
+	BPF_JCOND                                   = 0xe0
 	BPF_JEQ                                     = 0x10
 	BPF_JGE                                     = 0x30
 	BPF_JGT                                     = 0x20
@@ -657,6 +658,9 @@ const (
 	CAN_NPROTO                                  = 0x8
 	CAN_RAW                                     = 0x1
 	CAN_RAW_FILTER_MAX                          = 0x200
+	CAN_RAW_XL_VCID_RX_FILTER                   = 0x4
+	CAN_RAW_XL_VCID_TX_PASS                     = 0x2
+	CAN_RAW_XL_VCID_TX_SET                      = 0x1
 	CAN_RTR_FLAG                                = 0x40000000
 	CAN_SFF_ID_BITS                             = 0xb
 	CAN_SFF_MASK                                = 0x7ff
@@ -1339,6 +1343,7 @@ const (
 	F_OFD_SETLK                                 = 0x25
 	F_OFD_SETLKW                                = 0x26
 	F_OK                                        = 0x0
+	F_SEAL_EXEC                                 = 0x20
 	F_SEAL_FUTURE_WRITE                         = 0x10
 	F_SEAL_GROW                                 = 0x4
 	F_SEAL_SEAL                                 = 0x1
@@ -1627,6 +1632,7 @@ const (
 	IP_FREEBIND                                 = 0xf
 	IP_HDRINCL                                  = 0x3
 	IP_IPSEC_POLICY                             = 0x10
+	IP_LOCAL_PORT_RANGE                         = 0x33
 	IP_MAXPACKET                                = 0xffff
 	IP_MAX_MEMBERSHIPS                          = 0x14
 	IP_MF                                       = 0x2000
@@ -1653,6 +1659,7 @@ const (
 	IP_PMTUDISC_OMIT                            = 0x5
 	IP_PMTUDISC_PROBE                           = 0x3
 	IP_PMTUDISC_WANT                            = 0x1
+	IP_PROTOCOL                                 = 0x34
 	IP_RECVERR                                  = 0xb
 	IP_RECVERR_RFC4884                          = 0x1a
 	IP_RECVFRAGSIZE                             = 0x19
@@ -2169,7 +2176,7 @@ const (
 	NFT_SECMARK_CTX_MAXLEN                      = 0x100
 	NFT_SET_MAXNAMELEN                          = 0x100
 	NFT_SOCKET_MAX                              = 0x3
-	NFT_TABLE_F_MASK                            = 0x3
+	NFT_TABLE_F_MASK                            = 0x7
 	NFT_TABLE_MAXNAMELEN                        = 0x100
 	NFT_TRACETYPE_MAX                           = 0x3
 	NFT_TUNNEL_F_MASK                           = 0x7
@@ -2403,6 +2410,7 @@ const (
 	PERF_RECORD_MISC_USER                       = 0x2
 	PERF_SAMPLE_BRANCH_PLM_ALL                  = 0x7
 	PERF_SAMPLE_WEIGHT_TYPE                     = 0x1004000
+	PID_FS_MAGIC                                = 0x50494446
 	PIPEFS_MAGIC                                = 0x50495045
 	PPPIOCGNPMODE                               = 0xc008744c
 	PPPIOCNEWUNIT                               = 0xc004743e
@@ -2896,8 +2904,9 @@ const (
 	RWF_APPEND                                  = 0x10
 	RWF_DSYNC                                   = 0x2
 	RWF_HIPRI                                   = 0x1
+	RWF_NOAPPEND                                = 0x20
 	RWF_NOWAIT                                  = 0x8
-	RWF_SUPPORTED                               = 0x1f
+	RWF_SUPPORTED                               = 0x3f
 	RWF_SYNC                                    = 0x4
 	RWF_WRITE_LIFE_NOT_SET                      = 0x0
 	SCHED_BATCH                                 = 0x3
@@ -2918,7 +2927,9 @@ const (
 	SCHED_RESET_ON_FORK                         = 0x40000000
 	SCHED_RR                                    = 0x2
 	SCM_CREDENTIALS                             = 0x2
+	SCM_PIDFD                                   = 0x4
 	SCM_RIGHTS                                  = 0x1
+	SCM_SECURITY                                = 0x3
 	SCM_TIMESTAMP                               = 0x1d
 	SC_LOG_FLUSH                                = 0x100000
 	SECCOMP_ADDFD_FLAG_SEND                     = 0x2
@@ -3051,6 +3062,8 @@ const (
 	SIOCSMIIREG                                 = 0x8949
 	SIOCSRARP                                   = 0x8962
 	SIOCWANDEV                                  = 0x894a
+	SK_DIAG_BPF_STORAGE_MAX                     = 0x3
+	SK_DIAG_BPF_STORAGE_REQ_MAX                 = 0x1
 	SMACK_MAGIC                                 = 0x43415d53
 	SMART_AUTOSAVE                              = 0xd2
 	SMART_AUTO_OFFLINE                          = 0xdb
@@ -3071,6 +3084,8 @@ const (
 	SOCKFS_MAGIC                                = 0x534f434b
 	SOCK_BUF_LOCK_MASK                          = 0x3
 	SOCK_DCCP                                   = 0x6
+	SOCK_DESTROY                                = 0x15
+	SOCK_DIAG_BY_FAMILY                         = 0x14
 	SOCK_IOC_TYPE                               = 0x89
 	SOCK_PACKET                                 = 0xa
 	SOCK_RAW                                    = 0x3
@@ -3260,6 +3275,7 @@ const (
 	TCP_MAX_WINSHIFT                            = 0xe
 	TCP_MD5SIG                                  = 0xe
 	TCP_MD5SIG_EXT                              = 0x20
+	TCP_MD5SIG_FLAG_IFINDEX                     = 0x2
 	TCP_MD5SIG_FLAG_PREFIX                      = 0x1
 	TCP_MD5SIG_MAXKEYLEN                        = 0x50
 	TCP_MSS                                     = 0x200
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 42ff8c3c1b0..e4bc0bd57c7 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go
@@ -118,6 +118,7 @@ const (
 	IXOFF                            = 0x1000
 	IXON                             = 0x400
 	MAP_32BIT                        = 0x40
+	MAP_ABOVE4G                      = 0x80
 	MAP_ANON                         = 0x20
 	MAP_ANONYMOUS                    = 0x20
 	MAP_DENYWRITE                    = 0x800
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 dca436004fa..689317afdbf 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go
@@ -118,6 +118,7 @@ const (
 	IXOFF                            = 0x1000
 	IXON                             = 0x400
 	MAP_32BIT                        = 0x40
+	MAP_ABOVE4G                      = 0x80
 	MAP_ANON                         = 0x20
 	MAP_ANONYMOUS                    = 0x20
 	MAP_DENYWRITE                    = 0x800
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 d8cae6d1534..14270508b04 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go
@@ -87,6 +87,7 @@ const (
 	FICLONE                          = 0x40049409
 	FICLONERANGE                     = 0x4020940d
 	FLUSHO                           = 0x1000
+	FPMR_MAGIC                       = 0x46504d52
 	FPSIMD_MAGIC                     = 0x46508001
 	FS_IOC_ENABLE_VERITY             = 0x40806685
 	FS_IOC_GETFLAGS                  = 0x80086601
diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go
index 0036746ea19..4740b834854 100644
--- a/vendor/golang.org/x/sys/unix/ztypes_linux.go
+++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go
@@ -4605,7 +4605,7 @@ const (
 	NL80211_ATTR_MAC_HINT                                   = 0xc8
 	NL80211_ATTR_MAC_MASK                                   = 0xd7
 	NL80211_ATTR_MAX_AP_ASSOC_STA                           = 0xca
-	NL80211_ATTR_MAX                                        = 0x149
+	NL80211_ATTR_MAX                                        = 0x14a
 	NL80211_ATTR_MAX_CRIT_PROT_DURATION                     = 0xb4
 	NL80211_ATTR_MAX_CSA_COUNTERS                           = 0xce
 	NL80211_ATTR_MAX_MATCH_SETS                             = 0x85
@@ -5209,7 +5209,7 @@ const (
 	NL80211_FREQUENCY_ATTR_GO_CONCURRENT                    = 0xf
 	NL80211_FREQUENCY_ATTR_INDOOR_ONLY                      = 0xe
 	NL80211_FREQUENCY_ATTR_IR_CONCURRENT                    = 0xf
-	NL80211_FREQUENCY_ATTR_MAX                              = 0x1f
+	NL80211_FREQUENCY_ATTR_MAX                              = 0x20
 	NL80211_FREQUENCY_ATTR_MAX_TX_POWER                     = 0x6
 	NL80211_FREQUENCY_ATTR_NO_10MHZ                         = 0x11
 	NL80211_FREQUENCY_ATTR_NO_160MHZ                        = 0xc
@@ -5703,7 +5703,7 @@ const (
 	NL80211_STA_FLAG_ASSOCIATED                             = 0x7
 	NL80211_STA_FLAG_AUTHENTICATED                          = 0x5
 	NL80211_STA_FLAG_AUTHORIZED                             = 0x1
-	NL80211_STA_FLAG_MAX                                    = 0x7
+	NL80211_STA_FLAG_MAX                                    = 0x8
 	NL80211_STA_FLAG_MAX_OLD_API                            = 0x6
 	NL80211_STA_FLAG_MFP                                    = 0x4
 	NL80211_STA_FLAG_SHORT_PREAMBLE                         = 0x2
@@ -6001,3 +6001,34 @@ type CachestatRange struct {
 	Off uint64
 	Len uint64
 }
+
+const (
+	SK_MEMINFO_RMEM_ALLOC          = 0x0
+	SK_MEMINFO_RCVBUF              = 0x1
+	SK_MEMINFO_WMEM_ALLOC          = 0x2
+	SK_MEMINFO_SNDBUF              = 0x3
+	SK_MEMINFO_FWD_ALLOC           = 0x4
+	SK_MEMINFO_WMEM_QUEUED         = 0x5
+	SK_MEMINFO_OPTMEM              = 0x6
+	SK_MEMINFO_BACKLOG             = 0x7
+	SK_MEMINFO_DROPS               = 0x8
+	SK_MEMINFO_VARS                = 0x9
+	SKNLGRP_NONE                   = 0x0
+	SKNLGRP_INET_TCP_DESTROY       = 0x1
+	SKNLGRP_INET_UDP_DESTROY       = 0x2
+	SKNLGRP_INET6_TCP_DESTROY      = 0x3
+	SKNLGRP_INET6_UDP_DESTROY      = 0x4
+	SK_DIAG_BPF_STORAGE_REQ_NONE   = 0x0
+	SK_DIAG_BPF_STORAGE_REQ_MAP_FD = 0x1
+	SK_DIAG_BPF_STORAGE_REP_NONE   = 0x0
+	SK_DIAG_BPF_STORAGE            = 0x1
+	SK_DIAG_BPF_STORAGE_NONE       = 0x0
+	SK_DIAG_BPF_STORAGE_PAD        = 0x1
+	SK_DIAG_BPF_STORAGE_MAP_ID     = 0x2
+	SK_DIAG_BPF_STORAGE_MAP_VALUE  = 0x3
+)
+
+type SockDiagReq struct {
+	Family   uint8
+	Protocol uint8
+}
diff --git a/vendor/golang.org/x/sys/windows/security_windows.go b/vendor/golang.org/x/sys/windows/security_windows.go
index 26be94a8a7b..6f7d2ac70a9 100644
--- a/vendor/golang.org/x/sys/windows/security_windows.go
+++ b/vendor/golang.org/x/sys/windows/security_windows.go
@@ -68,6 +68,7 @@ type UserInfo10 struct {
 //sys	NetUserGetInfo(serverName *uint16, userName *uint16, level uint32, buf **byte) (neterr error) = netapi32.NetUserGetInfo
 //sys	NetGetJoinInformation(server *uint16, name **uint16, bufType *uint32) (neterr error) = netapi32.NetGetJoinInformation
 //sys	NetApiBufferFree(buf *byte) (neterr error) = netapi32.NetApiBufferFree
+//sys   NetUserEnum(serverName *uint16, level uint32, filter uint32, buf **byte, prefMaxLen uint32, entriesRead *uint32, totalEntries *uint32, resumeHandle *uint32) (neterr error) = netapi32.NetUserEnum
 
 const (
 	// do not reorder
diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go
index 5c6035ddfa9..9f73df75b5f 100644
--- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go
+++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go
@@ -401,6 +401,7 @@ var (
 	procTransmitFile                                         = modmswsock.NewProc("TransmitFile")
 	procNetApiBufferFree                                     = modnetapi32.NewProc("NetApiBufferFree")
 	procNetGetJoinInformation                                = modnetapi32.NewProc("NetGetJoinInformation")
+	procNetUserEnum                                          = modnetapi32.NewProc("NetUserEnum")
 	procNetUserGetInfo                                       = modnetapi32.NewProc("NetUserGetInfo")
 	procNtCreateFile                                         = modntdll.NewProc("NtCreateFile")
 	procNtCreateNamedPipeFile                                = modntdll.NewProc("NtCreateNamedPipeFile")
@@ -3486,6 +3487,14 @@ func NetGetJoinInformation(server *uint16, name **uint16, bufType *uint32) (nete
 	return
 }
 
+func NetUserEnum(serverName *uint16, level uint32, filter uint32, buf **byte, prefMaxLen uint32, entriesRead *uint32, totalEntries *uint32, resumeHandle *uint32) (neterr error) {
+	r0, _, _ := syscall.Syscall9(procNetUserEnum.Addr(), 8, uintptr(unsafe.Pointer(serverName)), uintptr(level), uintptr(filter), uintptr(unsafe.Pointer(buf)), uintptr(prefMaxLen), uintptr(unsafe.Pointer(entriesRead)), uintptr(unsafe.Pointer(totalEntries)), uintptr(unsafe.Pointer(resumeHandle)), 0)
+	if r0 != 0 {
+		neterr = syscall.Errno(r0)
+	}
+	return
+}
+
 func NetUserGetInfo(serverName *uint16, userName *uint16, level uint32, buf **byte) (neterr error) {
 	r0, _, _ := syscall.Syscall6(procNetUserGetInfo.Addr(), 4, uintptr(unsafe.Pointer(serverName)), uintptr(unsafe.Pointer(userName)), uintptr(level), uintptr(unsafe.Pointer(buf)), 0, 0)
 	if r0 != 0 {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 2a3b80693c7..719b4531ffc 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -2119,7 +2119,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.23.0
+# golang.org/x/crypto v0.24.0
 ## explicit; go 1.18
 golang.org/x/crypto/argon2
 golang.org/x/crypto/bcrypt
@@ -2171,7 +2171,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.25.0
+# golang.org/x/net v0.26.0
 ## explicit; go 1.18
 golang.org/x/net/bpf
 golang.org/x/net/context
@@ -2203,7 +2203,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.20.0
+# golang.org/x/sys v0.21.0
 ## explicit; go 1.18
 golang.org/x/sys/cpu
 golang.org/x/sys/execabs
@@ -2214,10 +2214,10 @@ 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.20.0
+# golang.org/x/term v0.21.0
 ## explicit; go 1.18
 golang.org/x/term
-# golang.org/x/text v0.15.0
+# golang.org/x/text v0.16.0
 ## explicit; go 1.18
 golang.org/x/text/cases
 golang.org/x/text/encoding
@@ -2245,7 +2245,7 @@ golang.org/x/text/width
 # golang.org/x/time v0.5.0
 ## explicit; go 1.18
 golang.org/x/time/rate
-# golang.org/x/tools v0.21.0
+# golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
 ## explicit; go 1.19
 golang.org/x/tools/cover
 golang.org/x/tools/go/ast/astutil


Test patch:
diff --git a/services/graph/pkg/service/v0/users_test.go b/services/graph/pkg/service/v0/users_test.go
index 80698251f37..8e51f6c3bd3 100644
--- a/services/graph/pkg/service/v0/users_test.go
+++ b/services/graph/pkg/service/v0/users_test.go
@@ -517,6 +517,16 @@ var _ = Describe("Users", func() {
 			identityBackend.On("GetGroupMembers", mock.Anything, "25cb7bc0-3168-4a0c-adbe-396f478ad494", mock.Anything).Return(users, nil)
 			identityBackend.On("GetGroupMembers", mock.Anything, "2713f1d5-6822-42bd-ad56-9f6c55a3a8fa", mock.Anything).Return([]*libregraph.User{}, nil)
 			identityBackend.On("GetUsers", mock.Anything, mock.Anything).Return([]*libregraph.User{user}, nil)
+			roleService.On("ListRoleAssignmentsFiltered", mock.Anything, mock.Anything, mock.Anything).
+				Return(func(ctx context.Context, in *settings.ListRoleAssignmentsFilteredRequest, opts ...client.CallOption) *settings.ListRoleAssignmentsResponse {
+					return &settings.ListRoleAssignmentsResponse{Assignments: []*settingsmsg.UserRoleAssignment{
+						{
+							Id:          "some-appRoleAssignment-ID",
+							AccountUuid: user.GetId(),
+							RoleId:      "some-appRole-ID",
+						},
+					}}
+				}, nil)
 			roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).
 				Return(func(ctx context.Context, in *settings.ListRoleAssignmentsRequest, opts ...client.CallOption) *settings.ListRoleAssignmentsResponse {
 					return &settings.ListRoleAssignmentsResponse{Assignments: []*settingsmsg.UserRoleAssignment{
diff --git a/tests/acceptance/features/apiSharingNg/listPermissions.feature b/tests/acceptance/features/apiSharingNg/listPermissions.feature
index ee598dfc2eb..345053f28e6 100644
--- a/tests/acceptance/features/apiSharingNg/listPermissions.feature
+++ b/tests/acceptance/features/apiSharingNg/listPermissions.feature
@@ -645,6 +645,7 @@ Feature: List a sharing permissions
       | Space Viewer     |
       | Space Editor     |
       | Manager          |
+      | Secure viewer    |
 
   @issues-8331
   Scenario: user lists permissions of a file in personal space
@@ -1681,6 +1682,7 @@ Feature: List a sharing permissions
       | Space Viewer     |
       | Space Editor     |
       | Manager          |
+      | Secure viewer    |
 
 
   Scenario: user sends share invitation with all allowed roles for a project space using root endpoint
diff --git a/vendor/golang.org/x/net/http2/testsync.go b/vendor/golang.org/x/net/http2/testsync.go
deleted file mode 100644
index 61075bd16d3..00000000000
--- a/vendor/golang.org/x/net/http2/testsync.go
+++ /dev/null
@@ -1,331 +0,0 @@
-// Copyright 2024 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-package http2
-
-import (
-	"context"
-	"sync"
-	"time"
-)
-
-// testSyncHooks coordinates goroutines in tests.
-//
-// For example, a call to ClientConn.RoundTrip involves several goroutines, including:
-//   - the goroutine running RoundTrip;
-//   - the clientStream.doRequest goroutine, which writes the request; and
-//   - the clientStream.readLoop goroutine, which reads the response.
-//
-// Using testSyncHooks, a test can start a RoundTrip and identify when all these goroutines
-// are blocked waiting for some condition such as reading the Request.Body or waiting for
-// flow control to become available.
-//
-// The testSyncHooks also manage timers and synthetic time in tests.
-// This permits us to, for example, start a request and cause it to time out waiting for
-// response headers without resorting to time.Sleep calls.
-type testSyncHooks struct {
-	// active/inactive act as a mutex and condition variable.
-	//
-	//  - neither chan contains a value: testSyncHooks is locked.
-	//  - active contains a value: unlocked, and at least one goroutine is not blocked
-	//  - inactive contains a value: unlocked, and all goroutines are blocked
-	active   chan struct{}
-	inactive chan struct{}
-
-	// goroutine counts
-	total    int                     // total goroutines
-	condwait map[*sync.Cond]int      // blocked in sync.Cond.Wait
-	blocked  []*testBlockedGoroutine // otherwise blocked
-
-	// fake time
-	now    time.Time
-	timers []*fakeTimer
-
-	// Transport testing: Report various events.
-	newclientconn func(*ClientConn)
-	newstream     func(*clientStream)
-}
-
-// testBlockedGoroutine is a blocked goroutine.
-type testBlockedGoroutine struct {
-	f  func() bool   // blocked until f returns true
-	ch chan struct{} // closed when unblocked
-}
-
-func newTestSyncHooks() *testSyncHooks {
-	h := &testSyncHooks{
-		active:   make(chan struct{}, 1),
-		inactive: make(chan struct{}, 1),
-		condwait: map[*sync.Cond]int{},
-	}
-	h.inactive <- struct{}{}
-	h.now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
-	return h
-}
-
-// lock acquires the testSyncHooks mutex.
-func (h *testSyncHooks) lock() {
-	select {
-	case <-h.active:
-	case <-h.inactive:
-	}
-}
-
-// waitInactive waits for all goroutines to become inactive.
-func (h *testSyncHooks) waitInactive() {
-	for {
-		<-h.inactive
-		if !h.unlock() {
-			break
-		}
-	}
-}
-
-// unlock releases the testSyncHooks mutex.
-// It reports whether any goroutines are active.
-func (h *testSyncHooks) unlock() (active bool) {
-	// Look for a blocked goroutine which can be unblocked.
-	blocked := h.blocked[:0]
-	unblocked := false
-	for _, b := range h.blocked {
-		if !unblocked && b.f() {
-			unblocked = true
-			close(b.ch)
-		} else {
-			blocked = append(blocked, b)
-		}
-	}
-	h.blocked = blocked
-
-	// Count goroutines blocked on condition variables.
-	condwait := 0
-	for _, count := range h.condwait {
-		condwait += count
-	}
-
-	if h.total > condwait+len(blocked) {
-		h.active <- struct{}{}
-		return true
-	} else {
-		h.inactive <- struct{}{}
-		return false
-	}
-}
-
-// goRun starts a new goroutine.
-func (h *testSyncHooks) goRun(f func()) {
-	h.lock()
-	h.total++
-	h.unlock()
-	go func() {
-		defer func() {
-			h.lock()
-			h.total--
-			h.unlock()
-		}()
-		f()
-	}()
-}
-
-// blockUntil indicates that a goroutine is blocked waiting for some condition to become true.
-// It waits until f returns true before proceeding.
-//
-// Example usage:
-//
-//	h.blockUntil(func() bool {
-//		// Is the context done yet?
-//		select {
-//		case <-ctx.Done():
-//		default:
-//			return false
-//		}
-//		return true
-//	})
-//	// Wait for the context to become done.
-//	<-ctx.Done()
-//
-// The function f passed to blockUntil must be non-blocking and idempotent.
-func (h *testSyncHooks) blockUntil(f func() bool) {
-	if f() {
-		return
-	}
-	ch := make(chan struct{})
-	h.lock()
-	h.blocked = append(h.blocked, &testBlockedGoroutine{
-		f:  f,
-		ch: ch,
-	})
-	h.unlock()
-	<-ch
-}
-
-// broadcast is sync.Cond.Broadcast.
-func (h *testSyncHooks) condBroadcast(cond *sync.Cond) {
-	h.lock()
-	delete(h.condwait, cond)
-	h.unlock()
-	cond.Broadcast()
-}
-
-// broadcast is sync.Cond.Wait.
-func (h *testSyncHooks) condWait(cond *sync.Cond) {
-	h.lock()
-	h.condwait[cond]++
-	h.unlock()
-}
-
-// newTimer creates a new fake timer.
-func (h *testSyncHooks) newTimer(d time.Duration) timer {
-	h.lock()
-	defer h.unlock()
-	t := &fakeTimer{
-		hooks: h,
-		when:  h.now.Add(d),
-		c:     make(chan time.Time),
-	}
-	h.timers = append(h.timers, t)
-	return t
-}
-
-// afterFunc creates a new fake AfterFunc timer.
-func (h *testSyncHooks) afterFunc(d time.Duration, f func()) timer {
-	h.lock()
-	defer h.unlock()
-	t := &fakeTimer{
-		hooks: h,
-		when:  h.now.Add(d),
-		f:     f,
-	}
-	h.timers = append(h.timers, t)
-	return t
-}
-
-func (h *testSyncHooks) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
-	ctx, cancel := context.WithCancel(ctx)
-	t := h.afterFunc(d, cancel)
-	return ctx, func() {
-		t.Stop()
-		cancel()
-	}
-}
-
-func (h *testSyncHooks) timeUntilEvent() time.Duration {
-	h.lock()
-	defer h.unlock()
-	var next time.Time
-	for _, t := range h.timers {
-		if next.IsZero() || t.when.Before(next) {
-			next = t.when
-		}
-	}
-	if d := next.Sub(h.now); d > 0 {
-		return d
-	}
-	return 0
-}
-
-// advance advances time and causes synthetic timers to fire.
-func (h *testSyncHooks) advance(d time.Duration) {
-	h.lock()
-	defer h.unlock()
-	h.now = h.now.Add(d)
-	timers := h.timers[:0]
-	for _, t := range h.timers {
-		t := t // remove after go.mod depends on go1.22
-		t.mu.Lock()
-		switch {
-		case t.when.After(h.now):
-			timers = append(timers, t)
-		case t.when.IsZero():
-			// stopped timer
-		default:
-			t.when = time.Time{}
-			if t.c != nil {
-				close(t.c)
-			}
-			if t.f != nil {
-				h.total++
-				go func() {
-					defer func() {
-						h.lock()
-						h.total--
-						h.unlock()
-					}()
-					t.f()
-				}()
-			}
-		}
-		t.mu.Unlock()
-	}
-	h.timers = timers
-}
-
-// A timer wraps a time.Timer, or a synthetic equivalent in tests.
-// Unlike time.Timer, timer is single-use: The timer channel is closed when the timer expires.
-type timer interface {
-	C() <-chan time.Time
-	Stop() bool
-	Reset(d time.Duration) bool
-}
-
-// timeTimer implements timer using real time.
-type timeTimer struct {
-	t *time.Timer
-	c chan time.Time
-}
-
-// newTimeTimer creates a new timer using real time.
-func newTimeTimer(d time.Duration) timer {
-	ch := make(chan time.Time)
-	t := time.AfterFunc(d, func() {
-		close(ch)
-	})
-	return &timeTimer{t, ch}
-}
-
-// newTimeAfterFunc creates an AfterFunc timer using real time.
-func newTimeAfterFunc(d time.Duration, f func()) timer {
-	return &timeTimer{
-		t: time.AfterFunc(d, f),
-	}
-}
-
-func (t timeTimer) C() <-chan time.Time        { return t.c }
-func (t timeTimer) Stop() bool                 { return t.t.Stop() }
-func (t timeTimer) Reset(d time.Duration) bool { return t.t.Reset(d) }
-
-// fakeTimer implements timer using fake time.
-type fakeTimer struct {
-	hooks *testSyncHooks
-
-	mu   sync.Mutex
-	when time.Time      // when the timer will fire
-	c    chan time.Time // closed when the timer fires; mutually exclusive with f
-	f    func()         // called when the timer fires; mutually exclusive with c
-}
-
-func (t *fakeTimer) C() <-chan time.Time { return t.c }
-
-func (t *fakeTimer) Stop() bool {
-	t.mu.Lock()
-	defer t.mu.Unlock()
-	stopped := t.when.IsZero()
-	t.when = time.Time{}
-	return stopped
-}
-
-func (t *fakeTimer) Reset(d time.Duration) bool {
-	if t.c != nil || t.f == nil {
-		panic("fakeTimer only supports Reset on AfterFunc timers")
-	}
-	t.mu.Lock()
-	defer t.mu.Unlock()
-	t.hooks.lock()
-	defer t.hooks.unlock()
-	active := !t.when.IsZero()
-	t.when = t.hooks.now.Add(d)
-	if !active {
-		t.hooks.timers = append(t.hooks.timers, t)
-	}
-	return active
-}