From 19141c2b71eba27c354730f94d591e135c75d1b2 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 13 Aug 2025 16:44:28 +0200 Subject: [PATCH] add user soft delete Signed-off-by: Christian Richter --- go.mod | 10 +- go.sum | 22 ++-- services/graph/pkg/config/config.go | 3 + .../pkg/config/defaults/defaultconfig.go | 1 + services/graph/pkg/service/v0/users.go | 95 +++++++++++----- services/graph/pkg/service/v0/users_test.go | 102 ++++++++++++++++++ .../go-jose/go-jose/v4/CHANGELOG.md | 5 + vendor/github.com/go-jose/go-jose/v4/jwe.go | 19 +++- vendor/github.com/go-jose/go-jose/v4/jwk.go | 2 +- vendor/github.com/go-jose/go-jose/v4/jws.go | 74 ++++++++++--- .../go-jose/go-jose/v4/symmetric.go | 12 ++- .../go-jose/go-jose/v4/symmetric_go124.go | 28 +++++ .../go-jose/go-jose/v4/symmetric_legacy.go | 29 +++++ .../opencloud-eu/reva/v2/pkg/events/users.go | 17 +++ vendor/google.golang.org/grpc/MAINTAINERS.md | 8 +- .../endpointsharding/endpointsharding.go | 21 +++- .../pickfirst/pickfirstleaf/pickfirstleaf.go | 6 +- vendor/google.golang.org/grpc/clientconn.go | 11 +- .../grpc/credentials/credentials.go | 25 ++++- .../google.golang.org/grpc/credentials/tls.go | 30 +++--- vendor/google.golang.org/grpc/dialoptions.go | 2 + .../grpc/internal/envconfig/envconfig.go | 16 ++- .../grpc/internal/internal.go | 29 ----- .../internal/resolver/dns/dns_resolver.go | 20 ++-- .../google.golang.org/grpc/picker_wrapper.go | 36 +++---- .../grpc/resolver/resolver.go | 5 + vendor/google.golang.org/grpc/server.go | 1 + vendor/google.golang.org/grpc/stats/stats.go | 20 ++-- vendor/google.golang.org/grpc/stream.go | 32 +++++- vendor/google.golang.org/grpc/version.go | 2 +- vendor/modules.txt | 13 +-- 31 files changed, 516 insertions(+), 180 deletions(-) create mode 100644 vendor/github.com/go-jose/go-jose/v4/symmetric_go124.go create mode 100644 vendor/github.com/go-jose/go-jose/v4/symmetric_legacy.go diff --git a/go.mod b/go.mod index 22d6110b93..762a77ed7a 100644 --- a/go.mod +++ b/go.mod @@ -105,8 +105,8 @@ require ( golang.org/x/sync v0.16.0 golang.org/x/term v0.34.0 golang.org/x/text v0.28.0 - google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 - google.golang.org/grpc v1.74.0 + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 + google.golang.org/grpc v1.75.0 google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v2 v2.4.0 gotest.tools/v3 v3.5.2 @@ -187,7 +187,7 @@ require ( github.com/go-git/go-git/v5 v5.13.2 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect - github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -332,7 +332,7 @@ require ( golang.org/x/tools v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect @@ -353,3 +353,5 @@ replace go-micro.dev/v4 => github.com/butonic/go-micro/v4 v4.11.1-0.202411151126 exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible replace github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a + +replace github.com/opencloud-eu/reva/v2 => github.com/dragonchaser/reva/v2 v2.0.0-20250825135357-d2d4b6e0c09d diff --git a/go.sum b/go.sum index 809eac5250..07dabe1fa1 100644 --- a/go.sum +++ b/go.sum @@ -281,6 +281,8 @@ github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg= +github.com/dragonchaser/reva/v2 v2.0.0-20250825135357-d2d4b6e0c09d h1:9uRftGdP0pABuXpiyj4kC27Jg8ahAzQR36cS6pq1UTk= +github.com/dragonchaser/reva/v2 v2.0.0-20250825135357-d2d4b6e0c09d/go.mod h1:CQAjf4rWvLb492qoKOau6W0ivmYOeU1EKb0+4ijzEtU= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e h1:rcHHSQqzCgvlwP0I/fQ8rQMn/MpHE5gWSLdtpxtP6KQ= @@ -360,8 +362,8 @@ github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3I github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -866,8 +868,6 @@ github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-202505121527 github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY= github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 h1:vD/EdfDUrv4omSFjrinT8Mvf+8D7f9g4vgQ2oiDrVUI= github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q= -github.com/opencloud-eu/reva/v2 v2.36.1-0.20250821084632-bd1139154d90 h1:DZ9t/jjqZ9swuS1bObUWoWn35PW4fQqbAI1k3UVCzNA= -github.com/opencloud-eu/reva/v2 v2.36.1-0.20250821084632-bd1139154d90/go.mod h1:5UqnN7ln0fHvIK5mwf+4zfIk++17jjJsOiI2hqi8TVA= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= @@ -1546,6 +1546,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -1601,10 +1603,10 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1620,8 +1622,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.74.0 h1:sxRSkyLxlceWQiqDofxDot3d4u7DyoHPc7SBXMj8gGY= -google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e h1:m7aQHHqd0q89mRwhwS9Bx2rjyl/hsFAeta+uGrHsQaU= google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e/go.mod h1:gID3PKrg7pWKntu9Ss6zTLJ0ttC0X9IHgREOCZwbCVU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/services/graph/pkg/config/config.go b/services/graph/pkg/config/config.go index bad9728278..13075a79a7 100644 --- a/services/graph/pkg/config/config.go +++ b/services/graph/pkg/config/config.go @@ -2,6 +2,7 @@ package config import ( "context" + "time" "github.com/opencloud-eu/opencloud/pkg/shared" ) @@ -39,6 +40,8 @@ type Config struct { Context context.Context `yaml:"-"` Metadata Metadata `yaml:"metadata_config"` + + UserSoftDeleteRetentionTime time.Duration `yaml:"user_soft_delete_retention_time" env:"GRAPH_USER_SOFT_DELETE_RETENTION_TIME" desc:"The time after which a soft-deleted user is permanently deleted. If set to 0 (default), there is no soft delete retention time and users are deleted immediately after being soft-deleted. If set to a positive value, the user will be kept in the system for that duration before being permanently deleted." introductionVersion:"%%NEXT%%"` } type Spaces struct { diff --git a/services/graph/pkg/config/defaults/defaultconfig.go b/services/graph/pkg/config/defaults/defaultconfig.go index 21888f1aa3..1cd0cdd769 100644 --- a/services/graph/pkg/config/defaults/defaultconfig.go +++ b/services/graph/pkg/config/defaults/defaultconfig.go @@ -131,6 +131,7 @@ func DefaultConfig() *config.Config { StorageAddress: "eu.opencloud.api.storage-system", SystemUserIDP: "internal", }, + UserSoftDeleteRetentionTime: 0, } } diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go index 5647feca96..f6721967ab 100644 --- a/services/graph/pkg/service/v0/users.go +++ b/services/graph/pkg/service/v0/users.go @@ -12,11 +12,13 @@ import ( "sort" "strconv" "strings" + "time" "github.com/CiscoM31/godata" invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + v1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/go-chi/chi/v5" "github.com/go-chi/render" "github.com/google/uuid" @@ -606,6 +608,8 @@ func getUserLanguage(ctx context.Context, valueService settingssvc.ValueService, // DeleteUser implements the Service interface. func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { + purgeUser := r.Header.Get("Prefer") == "purge" + logger := g.logger.SubloggerWithRequestID(r.Context()) logger.Debug().Msg("calling delete user") sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") @@ -638,14 +642,23 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { return } - e := events.UserDeleted{UserID: user.GetId()} - if currentUser, ok := revactx.ContextGetUser(r.Context()); ok { - if currentUser.GetId().GetOpaqueId() == user.GetId() { - logger.Debug().Msg("could not delete user: self deletion forbidden") - errorcode.NotAllowed.Render(w, r, http.StatusForbidden, "self deletion forbidden") - return - } - e.Executant = currentUser.GetId() + if g.config.UserSoftDeleteRetentionTime > 0 && purgeUser && user.GetAccountEnabled() { + logger.Debug().Msg("could not delete user: purgeUser is set but user is still enabled") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user should be hard deleted, but is still enabled, please soft delete first") + return + } + + currentUser, ok := revactx.ContextGetUser(r.Context()) + if !ok { + logger.Debug().Msg("could not delete user: user not in context") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context") + return + } + + if currentUser.GetId().GetOpaqueId() == user.GetId() { + logger.Debug().Msg("could not delete user: self deletion forbidden") + errorcode.NotAllowed.Render(w, r, http.StatusForbidden, "self deletion forbidden") + return } if g.gatewaySelector != nil { @@ -692,34 +705,60 @@ func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { return } } - purgeFlag := utils.AppendPlainToOpaque(nil, "purge", "") - _, err := client.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ - Opaque: purgeFlag, - Id: &storageprovider.StorageSpaceId{ - OpaqueId: sp.Id.OpaqueId, - }, - }) - if err != nil { - // transport error, log as error - logger.Error().Err(err).Msg("could not delete homespace: transport error") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not delete homespace, aborting") - return + // the space will if the system does not have a UserSoftDeleteRetentionTime configured, e.g. SoftDelete disabled + if g.config.UserSoftDeleteRetentionTime == 0 || (purgeUser && !user.GetAccountEnabled()) { + purgeSpaceFlag := utils.AppendPlainToOpaque(nil, "purge", "") + _, err := client.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{ + Opaque: purgeSpaceFlag, + Id: &storageprovider.StorageSpaceId{ + OpaqueId: sp.Id.OpaqueId, + }, + }) + if err != nil { + // transport error, log as error + logger.Error().Err(err).Msg("could not delete homespace: transport error") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not delete homespace, aborting") + return + } } break } } - logger.Debug().Str("id", user.GetId()).Msg("calling delete user on backend") - err = g.identityBackend.DeleteUser(r.Context(), user.GetId()) + if g.config.UserSoftDeleteRetentionTime == 0 || (purgeUser && !user.GetAccountEnabled()) { + logger.Debug().Str("id", user.GetId()).Msg("calling delete user on backend") + err = g.identityBackend.DeleteUser(r.Context(), user.GetId()) - if err != nil { - logger.Debug().Err(err).Msg("could not delete user: backend error") - errorcode.RenderError(w, r, err) - return + if err != nil { + logger.Debug().Err(err).Msg("could not delete user: backend error") + errorcode.RenderError(w, r, err) + return + } + } else { + logger.Debug().Str("id", user.GetId()).Msg("calling soft delete user on backend") + userUpdate := *libregraph.NewUserUpdate() + userUpdate.AccountEnabled = libregraph.PtrBool(false) + g.identityBackend.UpdateUser(r.Context(), user.GetId(), userUpdate) } - g.publishEvent(r.Context(), e) - + if g.config.UserSoftDeleteRetentionTime == 0 || + (g.config.UserSoftDeleteRetentionTime > 0 && purgeUser && !user.GetAccountEnabled()) { + e := events.UserDeleted{UserID: user.GetId()} + e.Executant = currentUser.GetId() + g.publishEvent(r.Context(), e) + } else { + e := events.UserSoftDeleted{ + UserID: user.GetId(), + RetentionTime: g.config.UserSoftDeleteRetentionTime, + Timestamp: &v1beta1.Timestamp{ + Seconds: uint64(time.Now().Unix()), + Nanos: uint32(time.Now().Nanosecond()), + }, + Reason: "User deleted via Graph API", // TODO: this needs a proper implementation through the request + } + e.Executant = currentUser.GetId() + g.publishEvent(r.Context(), e) + } render.Status(r, http.StatusNoContent) render.NoContent(w, r) } diff --git a/services/graph/pkg/service/v0/users_test.go b/services/graph/pkg/service/v0/users_test.go index ba86651662..ff13e79e8d 100644 --- a/services/graph/pkg/service/v0/users_test.go +++ b/services/graph/pkg/service/v0/users_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -1014,6 +1015,107 @@ var _ = Describe("Users", func() { }) }) + Describe("SoftDeleteUser", func() { + var ( + user *libregraph.User + //userUpdate *libregraph.UserUpdate + expectedUser *libregraph.User + ) + + BeforeEach(func() { + cfg.UserSoftDeleteRetentionTime = 30 * 24 * time.Hour // 30 days + user = libregraph.NewUser("Display Name", "user") + user.SetMail("user@example.com") + user.SetId("/users/user") + + //userUpdate = libregraph.NewUserUpdate() + + expectedUser = libregraph.NewUser("Display Name", "user") + expectedUser.SetMail(user.GetMail()) + expectedUser.SetId(user.GetId()) + + identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(user, nil) + }) + + It("soft deletes a user", func() { + otheruser := &userv1beta1.User{ + Id: &userv1beta1.UserId{ + OpaqueId: "otheruser", + }, + } + + lu := libregraph.User{} + lu.SetId(otheruser.Id.OpaqueId) + identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(&lu, nil) + identityBackend.On("DeleteUser", mock.Anything, mock.Anything).Return(nil) + identityBackend.On("UpdateUser", mock.Anything, mock.Anything, mock.Anything).Return(&lu, nil) + gatewayClient.On("DeleteStorageSpace", mock.Anything, mock.Anything).Return(&provider.DeleteStorageSpaceResponse{ + Status: status.NewOK(ctx), + }, nil) + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: []*provider.StorageSpace{ + { + Opaque: &typesv1beta1.Opaque{}, + Id: &provider.StorageSpaceId{OpaqueId: "drive1"}, + Root: &provider.ResourceId{SpaceId: "space", OpaqueId: "space"}, + SpaceType: "personal", + Owner: otheruser, + }, + }, + }, nil) + + r := httptest.NewRequest(http.MethodDelete, "/graph/v1.0/users/{userid}", nil) + rctx := chi.NewRouteContext() + rctx.URLParams.Add("userID", lu.GetId()) + r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx)) + svc.DeleteUser(rr, r) + + Expect(rr.Code).To(Equal(http.StatusNoContent)) + gatewayClient.AssertNumberOfCalls(GinkgoT(), "DeleteStorageSpace", 0) // 2 calls for the home space. first trash, then purge + + }) + + It("hard deletes the user", func() { + otheruser := &userv1beta1.User{ + Id: &userv1beta1.UserId{ + OpaqueId: "otheruser", + }, + } + + lu := libregraph.User{} + lu.SetId(otheruser.Id.OpaqueId) + identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(&lu, nil) + identityBackend.On("DeleteUser", mock.Anything, mock.Anything).Return(nil) + identityBackend.On("UpdateUser", mock.Anything, mock.Anything, mock.Anything).Return(&lu, nil) + gatewayClient.On("DeleteStorageSpace", mock.Anything, mock.Anything).Return(&provider.DeleteStorageSpaceResponse{ + Status: status.NewOK(ctx), + }, nil) + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: []*provider.StorageSpace{ + { + Opaque: &typesv1beta1.Opaque{}, + Id: &provider.StorageSpaceId{OpaqueId: "drive1"}, + Root: &provider.ResourceId{SpaceId: "space", OpaqueId: "space"}, + SpaceType: "personal", + Owner: otheruser, + }, + }, + }, nil) + + r := httptest.NewRequest(http.MethodDelete, "/graph/v1.0/users/{userid}", nil) + r.Header.Set("Prefer", "purge") // this header is used to indicate a hard delete + rctx := chi.NewRouteContext() + rctx.URLParams.Add("userID", lu.GetId()) + r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx)) + svc.DeleteUser(rr, r) + + Expect(rr.Code).To(Equal(http.StatusNoContent)) + gatewayClient.AssertNumberOfCalls(GinkgoT(), "DeleteStorageSpace", 0) // 0 calls for the home space. since we are "just" soft deleting + }) + }) + Describe("PatchUser", func() { var ( user *libregraph.User diff --git a/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md b/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md index 6f717dbd86..66a8a0f89a 100644 --- a/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md +++ b/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md @@ -1,3 +1,8 @@ +## Changed + + - Defined a custom error, ErrUnexpectedSignatureAlgorithm, returned when a JWS + header contains an unsupported signature algorithm. + # v4.0.4 ## Fixed diff --git a/vendor/github.com/go-jose/go-jose/v4/jwe.go b/vendor/github.com/go-jose/go-jose/v4/jwe.go index 9f1322dccc..6102f91000 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jwe.go +++ b/vendor/github.com/go-jose/go-jose/v4/jwe.go @@ -274,7 +274,7 @@ func validateAlgEnc(headers rawHeader, keyAlgorithms []KeyAlgorithm, contentEncr if alg != "" && !containsKeyAlgorithm(keyAlgorithms, alg) { return fmt.Errorf("unexpected key algorithm %q; expected %q", alg, keyAlgorithms) } - if alg != "" && !containsContentEncryption(contentEncryption, enc) { + if enc != "" && !containsContentEncryption(contentEncryption, enc) { return fmt.Errorf("unexpected content encryption algorithm %q; expected %q", enc, contentEncryption) } return nil @@ -288,11 +288,20 @@ func ParseEncryptedCompact( keyAlgorithms []KeyAlgorithm, contentEncryption []ContentEncryption, ) (*JSONWebEncryption, error) { - // Five parts is four separators - if strings.Count(input, ".") != 4 { - return nil, fmt.Errorf("go-jose/go-jose: compact JWE format must have five parts") + var parts [5]string + var ok bool + + for i := range 4 { + parts[i], input, ok = strings.Cut(input, ".") + if !ok { + return nil, errors.New("go-jose/go-jose: compact JWE format must have five parts") + } } - parts := strings.SplitN(input, ".", 5) + // Validate that the last part does not contain more dots + if strings.ContainsRune(input, '.') { + return nil, errors.New("go-jose/go-jose: compact JWE format must have five parts") + } + parts[4] = input rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { diff --git a/vendor/github.com/go-jose/go-jose/v4/jwk.go b/vendor/github.com/go-jose/go-jose/v4/jwk.go index 9e57e93ba2..9700f8906c 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jwk.go +++ b/vendor/github.com/go-jose/go-jose/v4/jwk.go @@ -239,7 +239,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) { keyPub = key } } else { - return fmt.Errorf("go-jose/go-jose: unknown curve %s'", raw.Crv) + return fmt.Errorf("go-jose/go-jose: unknown curve '%s'", raw.Crv) } default: return fmt.Errorf("go-jose/go-jose: unknown json web key type '%s'", raw.Kty) diff --git a/vendor/github.com/go-jose/go-jose/v4/jws.go b/vendor/github.com/go-jose/go-jose/v4/jws.go index d09d8ba507..c40bd3ec10 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jws.go +++ b/vendor/github.com/go-jose/go-jose/v4/jws.go @@ -75,7 +75,14 @@ type Signature struct { original *rawSignatureInfo } -// ParseSigned parses a signed message in JWS Compact or JWS JSON Serialization. +// ParseSigned parses a signed message in JWS Compact or JWS JSON Serialization. Validation fails if +// the JWS is signed with an algorithm that isn't in the provided list of signature algorithms. +// Applications should decide for themselves which signature algorithms are acceptable. If you're +// not sure which signature algorithms your application might receive, consult the documentation of +// the program which provides them or the protocol that you are implementing. You can also try +// getting an example JWS and decoding it with a tool like https://jwt.io to see what its "alg" +// header parameter indicates. The signature on the JWS does not get validated during parsing. Call +// Verify() after parsing to validate the signature and obtain the payload. // // https://datatracker.ietf.org/doc/html/rfc7515#section-7 func ParseSigned( @@ -90,7 +97,14 @@ func ParseSigned( return parseSignedCompact(signature, nil, signatureAlgorithms) } -// ParseSignedCompact parses a message in JWS Compact Serialization. +// ParseSignedCompact parses a message in JWS Compact Serialization. Validation fails if the JWS is +// signed with an algorithm that isn't in the provided list of signature algorithms. Applications +// should decide for themselves which signature algorithms are acceptable.If you're not sure which +// signature algorithms your application might receive, consult the documentation of the program +// which provides them or the protocol that you are implementing. You can also try getting an +// example JWS and decoding it with a tool like https://jwt.io to see what its "alg" header +// parameter indicates. The signature on the JWS does not get validated during parsing. Call +// Verify() after parsing to validate the signature and obtain the payload. // // https://datatracker.ietf.org/doc/html/rfc7515#section-7.1 func ParseSignedCompact( @@ -101,6 +115,15 @@ func ParseSignedCompact( } // ParseDetached parses a signed message in compact serialization format with detached payload. +// Validation fails if the JWS is signed with an algorithm that isn't in the provided list of +// signature algorithms. Applications should decide for themselves which signature algorithms are +// acceptable. If you're not sure which signature algorithms your application might receive, consult +// the documentation of the program which provides them or the protocol that you are implementing. +// You can also try getting an example JWS and decoding it with a tool like https://jwt.io to see +// what its "alg" header parameter indicates. The signature on the JWS does not get validated during +// parsing. Call Verify() after parsing to validate the signature and obtain the payload. +// +// https://datatracker.ietf.org/doc/html/rfc7515#appendix-F func ParseDetached( signature string, payload []byte, @@ -181,6 +204,25 @@ func containsSignatureAlgorithm(haystack []SignatureAlgorithm, needle SignatureA return false } +// ErrUnexpectedSignatureAlgorithm is returned when the signature algorithm in +// the JWS header does not match one of the expected algorithms. +type ErrUnexpectedSignatureAlgorithm struct { + // Got is the signature algorithm found in the JWS header. + Got SignatureAlgorithm + expected []SignatureAlgorithm +} + +func (e *ErrUnexpectedSignatureAlgorithm) Error() string { + return fmt.Sprintf("unexpected signature algorithm %q; expected %q", e.Got, e.expected) +} + +func newErrUnexpectedSignatureAlgorithm(got SignatureAlgorithm, expected []SignatureAlgorithm) error { + return &ErrUnexpectedSignatureAlgorithm{ + Got: got, + expected: expected, + } +} + // sanitized produces a cleaned-up JWS object from the raw JSON. func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgorithm) (*JSONWebSignature, error) { if len(signatureAlgorithms) == 0 { @@ -236,8 +278,7 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo alg := SignatureAlgorithm(signature.Header.Algorithm) if !containsSignatureAlgorithm(signatureAlgorithms, alg) { - return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q", - alg, signatureAlgorithms) + return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms) } if signature.header != nil { @@ -285,8 +326,7 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo alg := SignatureAlgorithm(obj.Signatures[i].Header.Algorithm) if !containsSignatureAlgorithm(signatureAlgorithms, alg) { - return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q", - alg, signatureAlgorithms) + return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms) } if obj.Signatures[i].header != nil { @@ -321,35 +361,43 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo return obj, nil } +const tokenDelim = "." + // parseSignedCompact parses a message in compact format. func parseSignedCompact( input string, payload []byte, signatureAlgorithms []SignatureAlgorithm, ) (*JSONWebSignature, error) { - // Three parts is two separators - if strings.Count(input, ".") != 2 { + protected, s, ok := strings.Cut(input, tokenDelim) + if !ok { // no period found + return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts") + } + claims, sig, ok := strings.Cut(s, tokenDelim) + if !ok { // only one period found + return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts") + } + if strings.ContainsRune(sig, '.') { // too many periods found return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts") } - parts := strings.SplitN(input, ".", 3) - if parts[1] != "" && payload != nil { + if claims != "" && payload != nil { return nil, fmt.Errorf("go-jose/go-jose: payload is not detached") } - rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0]) + rawProtected, err := base64.RawURLEncoding.DecodeString(protected) if err != nil { return nil, err } if payload == nil { - payload, err = base64.RawURLEncoding.DecodeString(parts[1]) + payload, err = base64.RawURLEncoding.DecodeString(claims) if err != nil { return nil, err } } - signature, err := base64.RawURLEncoding.DecodeString(parts[2]) + signature, err := base64.RawURLEncoding.DecodeString(sig) if err != nil { return nil, err } diff --git a/vendor/github.com/go-jose/go-jose/v4/symmetric.go b/vendor/github.com/go-jose/go-jose/v4/symmetric.go index a69103b084..6176e06074 100644 --- a/vendor/github.com/go-jose/go-jose/v4/symmetric.go +++ b/vendor/github.com/go-jose/go-jose/v4/symmetric.go @@ -30,8 +30,6 @@ import ( "hash" "io" - "golang.org/x/crypto/pbkdf2" - josecipher "github.com/go-jose/go-jose/v4/cipher" ) @@ -330,7 +328,10 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie // derive key keyLen, h := getPbkdf2Params(alg) - key := pbkdf2.Key(ctx.key, salt, ctx.p2c, keyLen, h) + key, err := pbkdf2Key(h, string(ctx.key), salt, ctx.p2c, keyLen) + if err != nil { + return recipientInfo{}, nil + } // use AES cipher with derived key block, err := aes.NewCipher(key) @@ -432,7 +433,10 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien // derive key keyLen, h := getPbkdf2Params(alg) - key := pbkdf2.Key(ctx.key, salt, p2c, keyLen, h) + key, err := pbkdf2Key(h, string(ctx.key), salt, p2c, keyLen) + if err != nil { + return nil, err + } // use AES cipher with derived key block, err := aes.NewCipher(key) diff --git a/vendor/github.com/go-jose/go-jose/v4/symmetric_go124.go b/vendor/github.com/go-jose/go-jose/v4/symmetric_go124.go new file mode 100644 index 0000000000..6c5a4e7f20 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/symmetric_go124.go @@ -0,0 +1,28 @@ +//go:build go1.24 + +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jose + +import ( + "crypto/pbkdf2" + "hash" +) + +func pbkdf2Key(h func() hash.Hash, password string, salt []byte, iter, keyLen int) ([]byte, error) { + return pbkdf2.Key(h, password, salt, iter, keyLen) +} diff --git a/vendor/github.com/go-jose/go-jose/v4/symmetric_legacy.go b/vendor/github.com/go-jose/go-jose/v4/symmetric_legacy.go new file mode 100644 index 0000000000..bdfc3d7663 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/symmetric_legacy.go @@ -0,0 +1,29 @@ +//go:build !go1.24 + +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jose + +import ( + "hash" + + "golang.org/x/crypto/pbkdf2" +) + +func pbkdf2Key(h func() hash.Hash, password string, salt []byte, iter, keyLen int) ([]byte, error) { + return pbkdf2.Key([]byte(password), salt, iter, keyLen, h), nil +} diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/events/users.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/events/users.go index 44a898f72d..cee613f48e 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/events/users.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/events/users.go @@ -20,6 +20,7 @@ package events import ( "encoding/json" + "time" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" @@ -53,6 +54,22 @@ func (UserDeleted) Unmarshal(v []byte) (interface{}, error) { return e, err } +// UserSoftDeleted is emitted when a user was soft-deleted +type UserSoftDeleted struct { + Executant *user.UserId + UserID string + Timestamp *types.Timestamp + RetentionTime time.Duration + Reason string +} + +// Unmarshal to fulfill umarshaller interface +func (UserSoftDeleted) Unmarshal(v []byte) (interface{}, error) { + e := UserSoftDeleted{} + err := json.Unmarshal(v, &e) + return e, err +} + // UserFeature represents a user feature type UserFeature struct { Name string diff --git a/vendor/google.golang.org/grpc/MAINTAINERS.md b/vendor/google.golang.org/grpc/MAINTAINERS.md index 5d4096d46a..df35bb9a88 100644 --- a/vendor/google.golang.org/grpc/MAINTAINERS.md +++ b/vendor/google.golang.org/grpc/MAINTAINERS.md @@ -9,21 +9,19 @@ for general contribution guidelines. ## Maintainers (in alphabetical order) -- [aranjans](https://github.com/aranjans), Google LLC - [arjan-bal](https://github.com/arjan-bal), Google LLC - [arvindbr8](https://github.com/arvindbr8), Google LLC - [atollena](https://github.com/atollena), Datadog, Inc. - [dfawley](https://github.com/dfawley), Google LLC - [easwars](https://github.com/easwars), Google LLC -- [erm-g](https://github.com/erm-g), Google LLC - [gtcooke94](https://github.com/gtcooke94), Google LLC -- [purnesh42h](https://github.com/purnesh42h), Google LLC -- [zasweq](https://github.com/zasweq), Google LLC ## Emeritus Maintainers (in alphabetical order) - [adelez](https://github.com/adelez) +- [aranjans](https://github.com/aranjans) - [canguler](https://github.com/canguler) - [cesarghali](https://github.com/cesarghali) +- [erm-g](https://github.com/erm-g) - [iamqizhao](https://github.com/iamqizhao) - [jeanbza](https://github.com/jeanbza) - [jtattermusch](https://github.com/jtattermusch) @@ -32,5 +30,7 @@ for general contribution guidelines. - [matt-kwong](https://github.com/matt-kwong) - [menghanl](https://github.com/menghanl) - [nicolasnoble](https://github.com/nicolasnoble) +- [purnesh42h](https://github.com/purnesh42h) - [srini100](https://github.com/srini100) - [yongni](https://github.com/yongni) +- [zasweq](https://github.com/zasweq) diff --git a/vendor/google.golang.org/grpc/balancer/endpointsharding/endpointsharding.go b/vendor/google.golang.org/grpc/balancer/endpointsharding/endpointsharding.go index 0ad6bb1f22..360db08ebc 100644 --- a/vendor/google.golang.org/grpc/balancer/endpointsharding/endpointsharding.go +++ b/vendor/google.golang.org/grpc/balancer/endpointsharding/endpointsharding.go @@ -37,6 +37,8 @@ import ( "google.golang.org/grpc/resolver" ) +var randIntN = rand.IntN + // ChildState is the balancer state of a child along with the endpoint which // identifies the child balancer. type ChildState struct { @@ -112,6 +114,21 @@ type endpointSharding struct { mu sync.Mutex } +// rotateEndpoints returns a slice of all the input endpoints rotated a random +// amount. +func rotateEndpoints(es []resolver.Endpoint) []resolver.Endpoint { + les := len(es) + if les == 0 { + return es + } + r := randIntN(les) + // Make a copy to avoid mutating data beyond the end of es. + ret := make([]resolver.Endpoint, les) + copy(ret, es[r:]) + copy(ret[les-r:], es[:r]) + return ret +} + // UpdateClientConnState creates a child for new endpoints and deletes children // for endpoints that are no longer present. It also updates all the children, // and sends a single synchronous update of the childrens' aggregated state at @@ -133,7 +150,7 @@ func (es *endpointSharding) UpdateClientConnState(state balancer.ClientConnState newChildren := resolver.NewEndpointMap[*balancerWrapper]() // Update/Create new children. - for _, endpoint := range state.ResolverState.Endpoints { + for _, endpoint := range rotateEndpoints(state.ResolverState.Endpoints) { if _, ok := newChildren.Get(endpoint); ok { // Endpoint child was already created, continue to avoid duplicate // update. @@ -279,7 +296,7 @@ func (es *endpointSharding) updateState() { p := &pickerWithChildStates{ pickers: pickers, childStates: childStates, - next: uint32(rand.IntN(len(pickers))), + next: uint32(randIntN(len(pickers))), } es.cc.UpdateState(balancer.State{ ConnectivityState: aggState, diff --git a/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirstleaf/pickfirstleaf.go b/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirstleaf/pickfirstleaf.go index e62047256a..67f315a0db 100644 --- a/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirstleaf/pickfirstleaf.go +++ b/vendor/google.golang.org/grpc/balancer/pickfirst/pickfirstleaf/pickfirstleaf.go @@ -67,21 +67,21 @@ var ( disconnectionsMetric = expstats.RegisterInt64Count(expstats.MetricDescriptor{ Name: "grpc.lb.pick_first.disconnections", Description: "EXPERIMENTAL. Number of times the selected subchannel becomes disconnected.", - Unit: "disconnection", + Unit: "{disconnection}", Labels: []string{"grpc.target"}, Default: false, }) connectionAttemptsSucceededMetric = expstats.RegisterInt64Count(expstats.MetricDescriptor{ Name: "grpc.lb.pick_first.connection_attempts_succeeded", Description: "EXPERIMENTAL. Number of successful connection attempts.", - Unit: "attempt", + Unit: "{attempt}", Labels: []string{"grpc.target"}, Default: false, }) connectionAttemptsFailedMetric = expstats.RegisterInt64Count(expstats.MetricDescriptor{ Name: "grpc.lb.pick_first.connection_attempts_failed", Description: "EXPERIMENTAL. Number of failed connection attempts.", - Unit: "attempt", + Unit: "{attempt}", Labels: []string{"grpc.target"}, Default: false, }) diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index cd3eaf8ddc..3f762285db 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -208,7 +208,7 @@ func NewClient(target string, opts ...DialOption) (conn *ClientConn, err error) channelz.Infof(logger, cc.channelz, "Channel authority set to %q", cc.authority) cc.csMgr = newConnectivityStateManager(cc.ctx, cc.channelz) - cc.pickerWrapper = newPickerWrapper(cc.dopts.copts.StatsHandlers) + cc.pickerWrapper = newPickerWrapper() cc.metricsRecorderList = stats.NewMetricsRecorderList(cc.dopts.copts.StatsHandlers) @@ -1076,13 +1076,6 @@ func (cc *ClientConn) healthCheckConfig() *healthCheckConfig { return cc.sc.healthCheckConfig } -func (cc *ClientConn) getTransport(ctx context.Context, failfast bool, method string) (transport.ClientTransport, balancer.PickResult, error) { - return cc.pickerWrapper.pick(ctx, failfast, balancer.PickInfo{ - Ctx: ctx, - FullMethodName: method, - }) -} - func (cc *ClientConn) applyServiceConfigAndBalancer(sc *ServiceConfig, configSelector iresolver.ConfigSelector) { if sc == nil { // should never reach here. @@ -1831,7 +1824,7 @@ func (cc *ClientConn) initAuthority() error { } else if auth, ok := cc.resolverBuilder.(resolver.AuthorityOverrider); ok { cc.authority = auth.OverrideAuthority(cc.parsedTarget) } else if strings.HasPrefix(endpoint, ":") { - cc.authority = "localhost" + endpoint + cc.authority = "localhost" + encodeAuthority(endpoint) } else { cc.authority = encodeAuthority(endpoint) } diff --git a/vendor/google.golang.org/grpc/credentials/credentials.go b/vendor/google.golang.org/grpc/credentials/credentials.go index a63ab606e6..c8e337cdda 100644 --- a/vendor/google.golang.org/grpc/credentials/credentials.go +++ b/vendor/google.golang.org/grpc/credentials/credentials.go @@ -96,10 +96,11 @@ func (c CommonAuthInfo) GetCommonAuthInfo() CommonAuthInfo { return c } -// ProtocolInfo provides information regarding the gRPC wire protocol version, -// security protocol, security protocol version in use, server name, etc. +// ProtocolInfo provides static information regarding transport credentials. type ProtocolInfo struct { // ProtocolVersion is the gRPC wire protocol version. + // + // Deprecated: this is unused by gRPC. ProtocolVersion string // SecurityProtocol is the security protocol in use. SecurityProtocol string @@ -109,7 +110,16 @@ type ProtocolInfo struct { // // Deprecated: please use Peer.AuthInfo. SecurityVersion string - // ServerName is the user-configured server name. + // ServerName is the user-configured server name. If set, this overrides + // the default :authority header used for all RPCs on the channel using the + // containing credentials, unless grpc.WithAuthority is set on the channel, + // in which case that setting will take precedence. + // + // This must be a valid `:authority` header according to + // [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2). + // + // Deprecated: Users should use grpc.WithAuthority to override the authority + // on a channel instead of configuring the credentials. ServerName string } @@ -173,12 +183,17 @@ type TransportCredentials interface { // Clone makes a copy of this TransportCredentials. Clone() TransportCredentials // OverrideServerName specifies the value used for the following: + // // - verifying the hostname on the returned certificates // - as SNI in the client's handshake to support virtual hosting // - as the value for `:authority` header at stream creation time // - // Deprecated: use grpc.WithAuthority instead. Will be supported - // throughout 1.x. + // The provided string should be a valid `:authority` header according to + // [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2). + // + // Deprecated: this method is unused by gRPC. Users should use + // grpc.WithAuthority to override the authority on a channel instead of + // configuring the credentials. OverrideServerName(string) error } diff --git a/vendor/google.golang.org/grpc/credentials/tls.go b/vendor/google.golang.org/grpc/credentials/tls.go index 20f65f7bd9..8277be7d6f 100644 --- a/vendor/google.golang.org/grpc/credentials/tls.go +++ b/vendor/google.golang.org/grpc/credentials/tls.go @@ -110,14 +110,14 @@ func (c tlsCreds) Info() ProtocolInfo { func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (_ net.Conn, _ AuthInfo, err error) { // use local cfg to avoid clobbering ServerName if using multiple endpoints cfg := credinternal.CloneTLSConfig(c.config) - if cfg.ServerName == "" { - serverName, _, err := net.SplitHostPort(authority) - if err != nil { - // If the authority had no host port or if the authority cannot be parsed, use it as-is. - serverName = authority - } - cfg.ServerName = serverName + + serverName, _, err := net.SplitHostPort(authority) + if err != nil { + // If the authority had no host port or if the authority cannot be parsed, use it as-is. + serverName = authority } + cfg.ServerName = serverName + conn := tls.Client(rawConn, cfg) errChannel := make(chan error, 1) go func() { @@ -259,9 +259,11 @@ func applyDefaults(c *tls.Config) *tls.Config { // certificates to establish the identity of the client need to be included in // the credentials (eg: for mTLS), use NewTLS instead, where a complete // tls.Config can be specified. -// serverNameOverride is for testing only. If set to a non empty string, -// it will override the virtual host name of authority (e.g. :authority header -// field) in requests. +// +// serverNameOverride is for testing only. If set to a non empty string, it will +// override the virtual host name of authority (e.g. :authority header field) in +// requests. Users should use grpc.WithAuthority passed to grpc.NewClient to +// override the authority of the client instead. func NewClientTLSFromCert(cp *x509.CertPool, serverNameOverride string) TransportCredentials { return NewTLS(&tls.Config{ServerName: serverNameOverride, RootCAs: cp}) } @@ -271,9 +273,11 @@ func NewClientTLSFromCert(cp *x509.CertPool, serverNameOverride string) Transpor // certificates to establish the identity of the client need to be included in // the credentials (eg: for mTLS), use NewTLS instead, where a complete // tls.Config can be specified. -// serverNameOverride is for testing only. If set to a non empty string, -// it will override the virtual host name of authority (e.g. :authority header -// field) in requests. +// +// serverNameOverride is for testing only. If set to a non empty string, it will +// override the virtual host name of authority (e.g. :authority header field) in +// requests. Users should use grpc.WithAuthority passed to grpc.NewClient to +// override the authority of the client instead. func NewClientTLSFromFile(certFile, serverNameOverride string) (TransportCredentials, error) { b, err := os.ReadFile(certFile) if err != nil { diff --git a/vendor/google.golang.org/grpc/dialoptions.go b/vendor/google.golang.org/grpc/dialoptions.go index ec0ca89ccd..7a5ac2e7c4 100644 --- a/vendor/google.golang.org/grpc/dialoptions.go +++ b/vendor/google.golang.org/grpc/dialoptions.go @@ -608,6 +608,8 @@ func WithChainStreamInterceptor(interceptors ...StreamClientInterceptor) DialOpt // WithAuthority returns a DialOption that specifies the value to be used as the // :authority pseudo-header and as the server name in authentication handshake. +// This overrides all other ways of setting authority on the channel, but can be +// overridden per-call by using grpc.CallAuthority. func WithAuthority(a string) DialOption { return newFuncDialOption(func(o *dialOptions) { o.authority = a diff --git a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go index 2fdaed88db..7e060f5ed1 100644 --- a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go +++ b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go @@ -26,26 +26,32 @@ import ( ) var ( - // TXTErrIgnore is set if TXT errors should be ignored ("GRPC_GO_IGNORE_TXT_ERRORS" is not "false"). + // EnableTXTServiceConfig is set if the DNS resolver should perform TXT + // lookups for service config ("GRPC_ENABLE_TXT_SERVICE_CONFIG" is not + // "false"). + EnableTXTServiceConfig = boolFromEnv("GRPC_ENABLE_TXT_SERVICE_CONFIG", true) + + // TXTErrIgnore is set if TXT errors should be ignored + // ("GRPC_GO_IGNORE_TXT_ERRORS" is not "false"). TXTErrIgnore = boolFromEnv("GRPC_GO_IGNORE_TXT_ERRORS", true) + // RingHashCap indicates the maximum ring size which defaults to 4096 // entries but may be overridden by setting the environment variable // "GRPC_RING_HASH_CAP". This does not override the default bounds // checking which NACKs configs specifying ring sizes > 8*1024*1024 (~8M). RingHashCap = uint64FromEnv("GRPC_RING_HASH_CAP", 4096, 1, 8*1024*1024) + // ALTSMaxConcurrentHandshakes is the maximum number of concurrent ALTS // handshakes that can be performed. ALTSMaxConcurrentHandshakes = uint64FromEnv("GRPC_ALTS_MAX_CONCURRENT_HANDSHAKES", 100, 1, 100) + // EnforceALPNEnabled is set if TLS connections to servers with ALPN disabled // should be rejected. The HTTP/2 protocol requires ALPN to be enabled, this // option is present for backward compatibility. This option may be overridden // by setting the environment variable "GRPC_ENFORCE_ALPN_ENABLED" to "true" // or "false". EnforceALPNEnabled = boolFromEnv("GRPC_ENFORCE_ALPN_ENABLED", true) - // XDSFallbackSupport is the env variable that controls whether support for - // xDS fallback is turned on. If this is unset or is false, only the first - // xDS server in the list of server configs will be used. - XDSFallbackSupport = boolFromEnv("GRPC_EXPERIMENTAL_XDS_FALLBACK", true) + // NewPickFirstEnabled is set if the new pickfirst leaf policy is to be used // instead of the exiting pickfirst implementation. This can be disabled by // setting the environment variable "GRPC_EXPERIMENTAL_ENABLE_NEW_PICK_FIRST" diff --git a/vendor/google.golang.org/grpc/internal/internal.go b/vendor/google.golang.org/grpc/internal/internal.go index 3ac798e8e6..2699223a27 100644 --- a/vendor/google.golang.org/grpc/internal/internal.go +++ b/vendor/google.golang.org/grpc/internal/internal.go @@ -182,35 +182,6 @@ var ( // other features, including the CSDS service. NewXDSResolverWithClientForTesting any // func(xdsclient.XDSClient) (resolver.Builder, error) - // RegisterRLSClusterSpecifierPluginForTesting registers the RLS Cluster - // Specifier Plugin for testing purposes, regardless of the XDSRLS environment - // variable. - // - // TODO: Remove this function once the RLS env var is removed. - RegisterRLSClusterSpecifierPluginForTesting func() - - // UnregisterRLSClusterSpecifierPluginForTesting unregisters the RLS Cluster - // Specifier Plugin for testing purposes. This is needed because there is no way - // to unregister the RLS Cluster Specifier Plugin after registering it solely - // for testing purposes using RegisterRLSClusterSpecifierPluginForTesting(). - // - // TODO: Remove this function once the RLS env var is removed. - UnregisterRLSClusterSpecifierPluginForTesting func() - - // RegisterRBACHTTPFilterForTesting registers the RBAC HTTP Filter for testing - // purposes, regardless of the RBAC environment variable. - // - // TODO: Remove this function once the RBAC env var is removed. - RegisterRBACHTTPFilterForTesting func() - - // UnregisterRBACHTTPFilterForTesting unregisters the RBAC HTTP Filter for - // testing purposes. This is needed because there is no way to unregister the - // HTTP Filter after registering it solely for testing purposes using - // RegisterRBACHTTPFilterForTesting(). - // - // TODO: Remove this function once the RBAC env var is removed. - UnregisterRBACHTTPFilterForTesting func() - // ORCAAllowAnyMinReportingInterval is for examples/orca use ONLY. ORCAAllowAnyMinReportingInterval any // func(so *orca.ServiceOptions) diff --git a/vendor/google.golang.org/grpc/internal/resolver/dns/dns_resolver.go b/vendor/google.golang.org/grpc/internal/resolver/dns/dns_resolver.go index ba5c5a95d0..ada5251cff 100644 --- a/vendor/google.golang.org/grpc/internal/resolver/dns/dns_resolver.go +++ b/vendor/google.golang.org/grpc/internal/resolver/dns/dns_resolver.go @@ -132,13 +132,13 @@ func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts // DNS address (non-IP). ctx, cancel := context.WithCancel(context.Background()) d := &dnsResolver{ - host: host, - port: port, - ctx: ctx, - cancel: cancel, - cc: cc, - rn: make(chan struct{}, 1), - disableServiceConfig: opts.DisableServiceConfig, + host: host, + port: port, + ctx: ctx, + cancel: cancel, + cc: cc, + rn: make(chan struct{}, 1), + enableServiceConfig: envconfig.EnableTXTServiceConfig && !opts.DisableServiceConfig, } d.resolver, err = internal.NewNetResolver(target.URL.Host) @@ -181,8 +181,8 @@ type dnsResolver struct { // finishes, race detector sometimes will warn lookup (READ the lookup // function pointers) inside watcher() goroutine has data race with // replaceNetFunc (WRITE the lookup function pointers). - wg sync.WaitGroup - disableServiceConfig bool + wg sync.WaitGroup + enableServiceConfig bool } // ResolveNow invoke an immediate resolution of the target that this @@ -346,7 +346,7 @@ func (d *dnsResolver) lookup() (*resolver.State, error) { if len(srv) > 0 { state = grpclbstate.Set(state, &grpclbstate.State{BalancerAddresses: srv}) } - if !d.disableServiceConfig { + if d.enableServiceConfig { state.ServiceConfig = d.lookupTXT(ctx) } return &state, nil diff --git a/vendor/google.golang.org/grpc/picker_wrapper.go b/vendor/google.golang.org/grpc/picker_wrapper.go index a2d2a798d4..aa52bfe95f 100644 --- a/vendor/google.golang.org/grpc/picker_wrapper.go +++ b/vendor/google.golang.org/grpc/picker_wrapper.go @@ -29,7 +29,6 @@ import ( "google.golang.org/grpc/internal/channelz" istatus "google.golang.org/grpc/internal/status" "google.golang.org/grpc/internal/transport" - "google.golang.org/grpc/stats" "google.golang.org/grpc/status" ) @@ -48,14 +47,11 @@ type pickerGeneration struct { // actions and unblock when there's a picker update. type pickerWrapper struct { // If pickerGen holds a nil pointer, the pickerWrapper is closed. - pickerGen atomic.Pointer[pickerGeneration] - statsHandlers []stats.Handler // to record blocking picker calls + pickerGen atomic.Pointer[pickerGeneration] } -func newPickerWrapper(statsHandlers []stats.Handler) *pickerWrapper { - pw := &pickerWrapper{ - statsHandlers: statsHandlers, - } +func newPickerWrapper() *pickerWrapper { + pw := &pickerWrapper{} pw.pickerGen.Store(&pickerGeneration{ blockingCh: make(chan struct{}), }) @@ -93,6 +89,12 @@ func doneChannelzWrapper(acbw *acBalancerWrapper, result *balancer.PickResult) { } } +type pick struct { + transport transport.ClientTransport // the selected transport + result balancer.PickResult // the contents of the pick from the LB policy + blocked bool // set if a picker call queued for a new picker +} + // pick returns the transport that will be used for the RPC. // It may block in the following cases: // - there's no picker @@ -100,15 +102,16 @@ func doneChannelzWrapper(acbw *acBalancerWrapper, result *balancer.PickResult) { // - the current picker returns other errors and failfast is false. // - the subConn returned by the current picker is not READY // When one of these situations happens, pick blocks until the picker gets updated. -func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.PickInfo) (transport.ClientTransport, balancer.PickResult, error) { +func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.PickInfo) (pick, error) { var ch chan struct{} var lastPickErr error + pickBlocked := false for { pg := pw.pickerGen.Load() if pg == nil { - return nil, balancer.PickResult{}, ErrClientConnClosing + return pick{}, ErrClientConnClosing } if pg.picker == nil { ch = pg.blockingCh @@ -127,9 +130,9 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer. } switch ctx.Err() { case context.DeadlineExceeded: - return nil, balancer.PickResult{}, status.Error(codes.DeadlineExceeded, errStr) + return pick{}, status.Error(codes.DeadlineExceeded, errStr) case context.Canceled: - return nil, balancer.PickResult{}, status.Error(codes.Canceled, errStr) + return pick{}, status.Error(codes.Canceled, errStr) } case <-ch: } @@ -145,9 +148,7 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer. // In the second case, the only way it will get to this conditional is // if there is a new picker. if ch != nil { - for _, sh := range pw.statsHandlers { - sh.HandleRPC(ctx, &stats.PickerUpdated{}) - } + pickBlocked = true } ch = pg.blockingCh @@ -164,7 +165,7 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer. if istatus.IsRestrictedControlPlaneCode(st) { err = status.Errorf(codes.Internal, "received picker error with illegal status: %v", err) } - return nil, balancer.PickResult{}, dropError{error: err} + return pick{}, dropError{error: err} } // For all other errors, wait for ready RPCs should block and other // RPCs should fail with unavailable. @@ -172,7 +173,7 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer. lastPickErr = err continue } - return nil, balancer.PickResult{}, status.Error(codes.Unavailable, err.Error()) + return pick{}, status.Error(codes.Unavailable, err.Error()) } acbw, ok := pickResult.SubConn.(*acBalancerWrapper) @@ -183,9 +184,8 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer. if t := acbw.ac.getReadyTransport(); t != nil { if channelz.IsOn() { doneChannelzWrapper(acbw, &pickResult) - return t, pickResult, nil } - return t, pickResult, nil + return pick{transport: t, result: pickResult, blocked: pickBlocked}, nil } if pickResult.Done != nil { // Calling done with nil error, no bytes sent and no bytes received. diff --git a/vendor/google.golang.org/grpc/resolver/resolver.go b/vendor/google.golang.org/grpc/resolver/resolver.go index b84ef26d46..8e6af9514b 100644 --- a/vendor/google.golang.org/grpc/resolver/resolver.go +++ b/vendor/google.golang.org/grpc/resolver/resolver.go @@ -332,6 +332,11 @@ type AuthorityOverrider interface { // OverrideAuthority returns the authority to use for a ClientConn with the // given target. The implementation must generate it without blocking, // typically in line, and must keep it unchanged. + // + // The returned string must be a valid ":authority" header value, i.e. be + // encoded according to + // [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2) as + // necessary. OverrideAuthority(Target) string } diff --git a/vendor/google.golang.org/grpc/server.go b/vendor/google.golang.org/grpc/server.go index 70fe23f550..1da2a542ac 100644 --- a/vendor/google.golang.org/grpc/server.go +++ b/vendor/google.golang.org/grpc/server.go @@ -1598,6 +1598,7 @@ func (s *Server) processStreamingRPC(ctx context.Context, stream *transport.Serv s: stream, p: &parser{r: stream, bufferPool: s.opts.bufferPool}, codec: s.getCodec(stream.ContentSubtype()), + desc: sd, maxReceiveMessageSize: s.opts.maxReceiveMessageSize, maxSendMessageSize: s.opts.maxSendMessageSize, trInfo: trInfo, diff --git a/vendor/google.golang.org/grpc/stats/stats.go b/vendor/google.golang.org/grpc/stats/stats.go index baf7740efb..10bf998aa5 100644 --- a/vendor/google.golang.org/grpc/stats/stats.go +++ b/vendor/google.golang.org/grpc/stats/stats.go @@ -64,15 +64,21 @@ func (s *Begin) IsClient() bool { return s.Client } func (s *Begin) isRPCStats() {} -// PickerUpdated indicates that the LB policy provided a new picker while the -// RPC was waiting for one. -type PickerUpdated struct{} +// DelayedPickComplete indicates that the RPC is unblocked following a delay in +// selecting a connection for the call. +type DelayedPickComplete struct{} -// IsClient indicates if the stats information is from client side. Only Client -// Side interfaces with a Picker, thus always returns true. -func (*PickerUpdated) IsClient() bool { return true } +// IsClient indicates DelayedPickComplete is available on the client. +func (*DelayedPickComplete) IsClient() bool { return true } -func (*PickerUpdated) isRPCStats() {} +func (*DelayedPickComplete) isRPCStats() {} + +// PickerUpdated indicates that the RPC is unblocked following a delay in +// selecting a connection for the call. +// +// Deprecated: will be removed in a future release; use DelayedPickComplete +// instead. +type PickerUpdated = DelayedPickComplete // InPayload contains stats about an incoming payload. type InPayload struct { diff --git a/vendor/google.golang.org/grpc/stream.go b/vendor/google.golang.org/grpc/stream.go index ca6948926f..d9bbd4c57c 100644 --- a/vendor/google.golang.org/grpc/stream.go +++ b/vendor/google.golang.org/grpc/stream.go @@ -469,8 +469,9 @@ func (cs *clientStream) newAttemptLocked(isTransparent bool) (*csAttempt, error) func (a *csAttempt) getTransport() error { cs := a.cs - var err error - a.transport, a.pickResult, err = cs.cc.getTransport(a.ctx, cs.callInfo.failFast, cs.callHdr.Method) + pickInfo := balancer.PickInfo{Ctx: a.ctx, FullMethodName: cs.callHdr.Method} + pick, err := cs.cc.pickerWrapper.pick(a.ctx, cs.callInfo.failFast, pickInfo) + a.transport, a.pickResult = pick.transport, pick.result if err != nil { if de, ok := err.(dropError); ok { err = de.error @@ -481,6 +482,11 @@ func (a *csAttempt) getTransport() error { if a.trInfo != nil { a.trInfo.firstLine.SetRemoteAddr(a.transport.RemoteAddr()) } + if pick.blocked { + for _, sh := range a.statsHandlers { + sh.HandleRPC(a.ctx, &stats.DelayedPickComplete{}) + } + } return nil } @@ -1580,6 +1586,7 @@ type serverStream struct { s *transport.ServerStream p *parser codec baseCodec + desc *StreamDesc compressorV0 Compressor compressorV1 encoding.Compressor @@ -1588,6 +1595,8 @@ type serverStream struct { sendCompressorName string + recvFirstMsg bool // set after the first message is received + maxReceiveMessageSize int maxSendMessageSize int trInfo *traceInfo @@ -1774,6 +1783,10 @@ func (ss *serverStream) RecvMsg(m any) (err error) { binlog.Log(ss.ctx, chc) } } + // Received no request msg for non-client streaming rpcs. + if !ss.desc.ClientStreams && !ss.recvFirstMsg { + return status.Error(codes.Internal, "cardinality violation: received no request message from non-client-streaming RPC") + } return err } if err == io.ErrUnexpectedEOF { @@ -1781,6 +1794,7 @@ func (ss *serverStream) RecvMsg(m any) (err error) { } return toRPCErr(err) } + ss.recvFirstMsg = true if len(ss.statsHandler) != 0 { for _, sh := range ss.statsHandler { sh.HandleRPC(ss.s.Context(), &stats.InPayload{ @@ -1800,7 +1814,19 @@ func (ss *serverStream) RecvMsg(m any) (err error) { binlog.Log(ss.ctx, cm) } } - return nil + + if ss.desc.ClientStreams { + // Subsequent messages should be received by subsequent RecvMsg calls. + return nil + } + // Special handling for non-client-stream rpcs. + // This recv expects EOF or errors, so we don't collect inPayload. + if err := recv(ss.p, ss.codec, ss.s, ss.decompressorV0, m, ss.maxReceiveMessageSize, nil, ss.decompressorV1, true); err == io.EOF { + return nil + } else if err != nil { + return err + } + return status.Error(codes.Internal, "cardinality violation: received multiple request messages for non-client-streaming RPC") } // MethodFromServerStream returns the method string for the input stream. diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index da68b32e14..bc1eb290f6 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.74.0-dev" +const Version = "1.75.0" diff --git a/vendor/modules.txt b/vendor/modules.txt index 292e717736..e01a609433 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -468,8 +468,8 @@ github.com/go-jose/go-jose/v3 github.com/go-jose/go-jose/v3/cipher github.com/go-jose/go-jose/v3/json github.com/go-jose/go-jose/v3/jwt -# github.com/go-jose/go-jose/v4 v4.0.5 -## explicit; go 1.21 +# github.com/go-jose/go-jose/v4 v4.1.1 +## explicit; go 1.23.0 github.com/go-jose/go-jose/v4 github.com/go-jose/go-jose/v4/cipher github.com/go-jose/go-jose/v4/json @@ -1161,7 +1161,7 @@ github.com/open-policy-agent/opa/v1/version # github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 ## explicit; go 1.18 github.com/opencloud-eu/libre-graph-api-go -# github.com/opencloud-eu/reva/v2 v2.36.1-0.20250821084632-bd1139154d90 +# github.com/opencloud-eu/reva/v2 v2.36.1-0.20250821084632-bd1139154d90 => github.com/dragonchaser/reva/v2 v2.0.0-20250825135357-d2d4b6e0c09d ## explicit; go 1.24.1 github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace github.com/opencloud-eu/reva/v2/cmd/revad/runtime @@ -2268,16 +2268,16 @@ golang.org/x/xerrors/internal # google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb ## explicit; go 1.23.0 google.golang.org/genproto/protobuf/field_mask -# google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 +# google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 ## explicit; go 1.23.0 google.golang.org/genproto/googleapis/api google.golang.org/genproto/googleapis/api/annotations google.golang.org/genproto/googleapis/api/httpbody -# google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 +# google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 ## explicit; go 1.23.0 google.golang.org/genproto/googleapis/rpc/errdetails google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.74.0 +# google.golang.org/grpc v1.75.0 ## explicit; go 1.23.0 google.golang.org/grpc google.golang.org/grpc/attributes @@ -2428,3 +2428,4 @@ stash.kopano.io/kgol/rndm # github.com/unrolled/secure => github.com/DeepDiver1975/secure v0.0.0-20240611112133-abc838fb797c # go-micro.dev/v4 => github.com/butonic/go-micro/v4 v4.11.1-0.20241115112658-b5d4de5ed9b3 # github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a +# github.com/opencloud-eu/reva/v2 => github.com/dragonchaser/reva/v2 v2.0.0-20250825135357-d2d4b6e0c09d