diff --git a/api/ttn/lorawan/v3/api.md b/api/ttn/lorawan/v3/api.md index 0b554fe0e6..42f0fe8d84 100644 --- a/api/ttn/lorawan/v3/api.md +++ b/api/ttn/lorawan/v3/api.md @@ -330,8 +330,10 @@ - [Message `GetGatewayConfigurationResponse`](#ttn.lorawan.v3.GetGatewayConfigurationResponse) - [Service `GatewayConfigurationService`](#ttn.lorawan.v3.GatewayConfigurationService) - [File `ttn/lorawan/v3/gateway_services.proto`](#ttn/lorawan/v3/gateway_services.proto) + - [Message `AssertGatewayRightsRequest`](#ttn.lorawan.v3.AssertGatewayRightsRequest) - [Message `PullGatewayConfigurationRequest`](#ttn.lorawan.v3.PullGatewayConfigurationRequest) - [Service `GatewayAccess`](#ttn.lorawan.v3.GatewayAccess) + - [Service `GatewayBatchAccess`](#ttn.lorawan.v3.GatewayBatchAccess) - [Service `GatewayConfigurator`](#ttn.lorawan.v3.GatewayConfigurator) - [Service `GatewayRegistry`](#ttn.lorawan.v3.GatewayRegistry) - [File `ttn/lorawan/v3/gatewayserver.proto`](#ttn/lorawan/v3/gatewayserver.proto) @@ -5000,6 +5002,20 @@ Identifies an end device model with version information. ## File `ttn/lorawan/v3/gateway_services.proto` +### Message `AssertGatewayRightsRequest` + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `gateway_ids` | [`GatewayIdentifiers`](#ttn.lorawan.v3.GatewayIdentifiers) | repeated | | +| `required` | [`Rights`](#ttn.lorawan.v3.Rights) | | | + +#### Field Rules + +| Field | Validations | +| ----- | ----------- | +| `gateway_ids` |
`repeated.min_items`: `1`
`repeated.max_items`: `20`
| +| `required` |`message.required`: `true`
| + ### Message `PullGatewayConfigurationRequest` | Field | Type | Label | Description | @@ -5009,7 +5025,7 @@ Identifies an end device model with version information. ### Service `GatewayAccess` -The GatewayAcces service, exposed by the Identity Server, is used to manage +The GatewayAccess service, exposed by the Identity Server, is used to manage API keys and collaborators of gateways. | Method Name | Request Type | Response Type | Description | @@ -5038,6 +5054,22 @@ API keys and collaborators of gateways. | `SetCollaborator` | `PUT` | `/api/v3/gateways/{gateway_ids.gateway_id}/collaborators` | `*` | | `ListCollaborators` | `GET` | `/api/v3/gateways/{gateway_ids.gateway_id}/collaborators` | | +### Service `GatewayBatchAccess` + +The GatewayBatchAccess service, exposed by the Identity Server, is used to +check the rights of the caller on multiple gateways at once. +EXPERIMENTAL: This service is subject to change. + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| `AssertRights` | [`AssertGatewayRightsRequest`](#ttn.lorawan.v3.AssertGatewayRightsRequest) | [`.google.protobuf.Empty`](#google.protobuf.Empty) | Assert that the caller has the requested rights on all the requested gateways. The check is successful if there are no errors. | + +#### HTTP bindings + +| Method Name | Method | Pattern | Body | +| ----------- | ------ | ------- | ---- | +| `AssertRights` | `GET` | `/api/v3/gateways/rights/batch` | | + ### Service `GatewayConfigurator` | Method Name | Request Type | Response Type | Description | @@ -5147,7 +5179,7 @@ GatewayUp may contain zero or more uplink messages and/or a status message for t | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | `GetGatewayConnectionStats` | [`GatewayIdentifiers`](#ttn.lorawan.v3.GatewayIdentifiers) | [`GatewayConnectionStats`](#ttn.lorawan.v3.GatewayConnectionStats) | Get statistics about the current gateway connection to the Gateway Server. This is not persisted between reconnects. | -| `BatchGetGatewayConnectionStats` | [`BatchGetGatewayConnectionStatsRequest`](#ttn.lorawan.v3.BatchGetGatewayConnectionStatsRequest) | [`BatchGetGatewayConnectionStatsResponse`](#ttn.lorawan.v3.BatchGetGatewayConnectionStatsResponse) | Get statistics about gateway connections to the Gateway Server of a batch of gateways. This is not persisted between reconnects. Gateways that are not connected or are part of a different cluster are ignored. It is up to the client to make sure that the gateways are in the requested cluster. | +| `BatchGetGatewayConnectionStats` | [`BatchGetGatewayConnectionStatsRequest`](#ttn.lorawan.v3.BatchGetGatewayConnectionStatsRequest) | [`BatchGetGatewayConnectionStatsResponse`](#ttn.lorawan.v3.BatchGetGatewayConnectionStatsResponse) | Get statistics about gateway connections to the Gateway Server of a batch of gateways. - Statistics are not persisted between reconnects. - Gateways that are not connected or are part of a different cluster are ignored. - The client should ensure that the requested gateways are in the requested cluster. - The client should have the right to get the gateway connection stats on all requested gateways. | #### HTTP bindings diff --git a/api/ttn/lorawan/v3/api.swagger.json b/api/ttn/lorawan/v3/api.swagger.json index 4ef4266a00..2f59837909 100644 --- a/api/ttn/lorawan/v3/api.swagger.json +++ b/api/ttn/lorawan/v3/api.swagger.json @@ -86,6 +86,9 @@ { "name": "GatewayConfigurator" }, + { + "name": "GatewayBatchAccess" + }, { "name": "GtwGs" }, @@ -8443,6 +8446,109 @@ ] } }, + "/gateways/rights/batch": { + "get": { + "summary": "Assert that the caller has the requested rights on all the requested gateways.\nThe check is successful if there are no errors.", + "operationId": "GatewayBatchAccess_AssertRights", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": {} + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "required.rights", + "description": " - RIGHT_USER_INFO: The right to view user information.\n - RIGHT_USER_SETTINGS_BASIC: The right to edit basic user settings.\n - RIGHT_USER_SETTINGS_API_KEYS: The right to view and edit user API keys.\n - RIGHT_USER_DELETE: The right to delete user account.\n - RIGHT_USER_AUTHORIZED_CLIENTS: The right to view and edit authorized OAuth clients of the user.\n - RIGHT_USER_APPLICATIONS_LIST: The right to list applications the user is a collaborator of.\n - RIGHT_USER_APPLICATIONS_CREATE: The right to create an application under the user account.\n - RIGHT_USER_GATEWAYS_LIST: The right to list gateways the user is a collaborator of.\n - RIGHT_USER_GATEWAYS_CREATE: The right to create a gateway under the account of the user.\n - RIGHT_USER_CLIENTS_LIST: The right to list OAuth clients the user is a collaborator of.\n - RIGHT_USER_CLIENTS_CREATE: The right to create an OAuth client under the account of the user.\n - RIGHT_USER_ORGANIZATIONS_LIST: The right to list organizations the user is a member of.\n - RIGHT_USER_ORGANIZATIONS_CREATE: The right to create an organization under the user account.\n - RIGHT_USER_NOTIFICATIONS_READ: The right to read notifications sent to the user.\n - RIGHT_USER_ALL: The pseudo-right for all (current and future) user rights.\n - RIGHT_APPLICATION_INFO: The right to view application information.\n - RIGHT_APPLICATION_SETTINGS_BASIC: The right to edit basic application settings.\n - RIGHT_APPLICATION_SETTINGS_API_KEYS: The right to view and edit application API keys.\n - RIGHT_APPLICATION_SETTINGS_COLLABORATORS: The right to view and edit application collaborators.\n - RIGHT_APPLICATION_SETTINGS_PACKAGES: The right to view and edit application packages and associations.\n - RIGHT_APPLICATION_DELETE: The right to delete application.\n - RIGHT_APPLICATION_DEVICES_READ: The right to view devices in application.\n - RIGHT_APPLICATION_DEVICES_WRITE: The right to create devices in application.\n - RIGHT_APPLICATION_DEVICES_READ_KEYS: The right to view device keys in application.\nNote that keys may not be stored in a way that supports viewing them.\n - RIGHT_APPLICATION_DEVICES_WRITE_KEYS: The right to edit device keys in application.\n - RIGHT_APPLICATION_TRAFFIC_READ: The right to read application traffic (uplink and downlink).\n - RIGHT_APPLICATION_TRAFFIC_UP_WRITE: The right to write uplink application traffic.\n - RIGHT_APPLICATION_TRAFFIC_DOWN_WRITE: The right to write downlink application traffic.\n - RIGHT_APPLICATION_LINK: The right to link as Application to a Network Server for traffic exchange,\ni.e. read uplink and write downlink (API keys only).\nThis right is typically only given to an Application Server.\nThis right implies RIGHT_APPLICATION_INFO, RIGHT_APPLICATION_TRAFFIC_READ,\nand RIGHT_APPLICATION_TRAFFIC_DOWN_WRITE.\n - RIGHT_APPLICATION_ALL: The pseudo-right for all (current and future) application rights.\n - RIGHT_CLIENT_ALL: The pseudo-right for all (current and future) OAuth client rights.\n - RIGHT_CLIENT_INFO: The right to read client information.\n - RIGHT_CLIENT_SETTINGS_BASIC: The right to edit basic client settings.\n - RIGHT_CLIENT_SETTINGS_COLLABORATORS: The right to view and edit client collaborators.\n - RIGHT_CLIENT_DELETE: The right to delete a client.\n - RIGHT_GATEWAY_INFO: The right to view gateway information.\n - RIGHT_GATEWAY_SETTINGS_BASIC: The right to edit basic gateway settings.\n - RIGHT_GATEWAY_SETTINGS_API_KEYS: The right to view and edit gateway API keys.\n - RIGHT_GATEWAY_SETTINGS_COLLABORATORS: The right to view and edit gateway collaborators.\n - RIGHT_GATEWAY_DELETE: The right to delete gateway.\n - RIGHT_GATEWAY_TRAFFIC_READ: The right to read gateway traffic.\n - RIGHT_GATEWAY_TRAFFIC_DOWN_WRITE: The right to write downlink gateway traffic.\n - RIGHT_GATEWAY_LINK: The right to link as Gateway to a Gateway Server for traffic exchange,\ni.e. write uplink and read downlink (API keys only)\nThis right is typically only given to a gateway.\nThis right implies RIGHT_GATEWAY_INFO.\n - RIGHT_GATEWAY_STATUS_READ: The right to view gateway status.\n - RIGHT_GATEWAY_LOCATION_READ: The right to view view gateway location.\n - RIGHT_GATEWAY_WRITE_SECRETS: The right to store secrets associated with this gateway.\n - RIGHT_GATEWAY_READ_SECRETS: The right to retrieve secrets associated with this gateway.\n - RIGHT_GATEWAY_ALL: The pseudo-right for all (current and future) gateway rights.\n - RIGHT_ORGANIZATION_INFO: The right to view organization information.\n - RIGHT_ORGANIZATION_SETTINGS_BASIC: The right to edit basic organization settings.\n - RIGHT_ORGANIZATION_SETTINGS_API_KEYS: The right to view and edit organization API keys.\n - RIGHT_ORGANIZATION_SETTINGS_MEMBERS: The right to view and edit organization members.\n - RIGHT_ORGANIZATION_DELETE: The right to delete organization.\n - RIGHT_ORGANIZATION_APPLICATIONS_LIST: The right to list the applications the organization is a collaborator of.\n - RIGHT_ORGANIZATION_APPLICATIONS_CREATE: The right to create an application under the organization.\n - RIGHT_ORGANIZATION_GATEWAYS_LIST: The right to list the gateways the organization is a collaborator of.\n - RIGHT_ORGANIZATION_GATEWAYS_CREATE: The right to create a gateway under the organization.\n - RIGHT_ORGANIZATION_CLIENTS_LIST: The right to list the OAuth clients the organization is a collaborator of.\n - RIGHT_ORGANIZATION_CLIENTS_CREATE: The right to create an OAuth client under the organization.\n - RIGHT_ORGANIZATION_ADD_AS_COLLABORATOR: The right to add the organization as a collaborator on an existing entity.\n - RIGHT_ORGANIZATION_ALL: The pseudo-right for all (current and future) organization rights.\n - RIGHT_SEND_INVITES: The right to send invites to new users.\nNote that this is not prefixed with \"USER_\"; it is not a right on the user entity.\n - RIGHT_ALL: The pseudo-right for all (current and future) possible rights.", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string", + "enum": [ + "right_invalid", + "RIGHT_USER_INFO", + "RIGHT_USER_SETTINGS_BASIC", + "RIGHT_USER_SETTINGS_API_KEYS", + "RIGHT_USER_DELETE", + "RIGHT_USER_AUTHORIZED_CLIENTS", + "RIGHT_USER_APPLICATIONS_LIST", + "RIGHT_USER_APPLICATIONS_CREATE", + "RIGHT_USER_GATEWAYS_LIST", + "RIGHT_USER_GATEWAYS_CREATE", + "RIGHT_USER_CLIENTS_LIST", + "RIGHT_USER_CLIENTS_CREATE", + "RIGHT_USER_ORGANIZATIONS_LIST", + "RIGHT_USER_ORGANIZATIONS_CREATE", + "RIGHT_USER_NOTIFICATIONS_READ", + "RIGHT_USER_ALL", + "RIGHT_APPLICATION_INFO", + "RIGHT_APPLICATION_SETTINGS_BASIC", + "RIGHT_APPLICATION_SETTINGS_API_KEYS", + "RIGHT_APPLICATION_SETTINGS_COLLABORATORS", + "RIGHT_APPLICATION_SETTINGS_PACKAGES", + "RIGHT_APPLICATION_DELETE", + "RIGHT_APPLICATION_DEVICES_READ", + "RIGHT_APPLICATION_DEVICES_WRITE", + "RIGHT_APPLICATION_DEVICES_READ_KEYS", + "RIGHT_APPLICATION_DEVICES_WRITE_KEYS", + "RIGHT_APPLICATION_TRAFFIC_READ", + "RIGHT_APPLICATION_TRAFFIC_UP_WRITE", + "RIGHT_APPLICATION_TRAFFIC_DOWN_WRITE", + "RIGHT_APPLICATION_LINK", + "RIGHT_APPLICATION_ALL", + "RIGHT_CLIENT_ALL", + "RIGHT_CLIENT_INFO", + "RIGHT_CLIENT_SETTINGS_BASIC", + "RIGHT_CLIENT_SETTINGS_COLLABORATORS", + "RIGHT_CLIENT_DELETE", + "RIGHT_GATEWAY_INFO", + "RIGHT_GATEWAY_SETTINGS_BASIC", + "RIGHT_GATEWAY_SETTINGS_API_KEYS", + "RIGHT_GATEWAY_SETTINGS_COLLABORATORS", + "RIGHT_GATEWAY_DELETE", + "RIGHT_GATEWAY_TRAFFIC_READ", + "RIGHT_GATEWAY_TRAFFIC_DOWN_WRITE", + "RIGHT_GATEWAY_LINK", + "RIGHT_GATEWAY_STATUS_READ", + "RIGHT_GATEWAY_LOCATION_READ", + "RIGHT_GATEWAY_WRITE_SECRETS", + "RIGHT_GATEWAY_READ_SECRETS", + "RIGHT_GATEWAY_ALL", + "RIGHT_ORGANIZATION_INFO", + "RIGHT_ORGANIZATION_SETTINGS_BASIC", + "RIGHT_ORGANIZATION_SETTINGS_API_KEYS", + "RIGHT_ORGANIZATION_SETTINGS_MEMBERS", + "RIGHT_ORGANIZATION_DELETE", + "RIGHT_ORGANIZATION_APPLICATIONS_LIST", + "RIGHT_ORGANIZATION_APPLICATIONS_CREATE", + "RIGHT_ORGANIZATION_GATEWAYS_LIST", + "RIGHT_ORGANIZATION_GATEWAYS_CREATE", + "RIGHT_ORGANIZATION_CLIENTS_LIST", + "RIGHT_ORGANIZATION_CLIENTS_CREATE", + "RIGHT_ORGANIZATION_ADD_AS_COLLABORATOR", + "RIGHT_ORGANIZATION_ALL", + "RIGHT_SEND_INVITES", + "RIGHT_ALL" + ] + }, + "collectionFormat": "multi" + } + ], + "tags": [ + "GatewayBatchAccess" + ] + } + }, "/gateways/{gateway.ids.gateway_id}": { "put": { "summary": "Update the gateway, changing the fields specified by the field mask to the provided values.", @@ -9690,7 +9796,7 @@ }, "/gs/gateways/connection/stats": { "post": { - "summary": "Get statistics about gateway connections to the Gateway Server of a batch of gateways.\nThis is not persisted between reconnects.\nGateways that are not connected or are part of a different cluster are ignored.\nIt is up to the client to make sure that the gateways are in the requested cluster.", + "summary": "Get statistics about gateway connections to the Gateway Server of a batch of gateways.\n- Statistics are not persisted between reconnects.\n- Gateways that are not connected or are part of a different cluster are ignored.\n- The client should ensure that the requested gateways are in the requested cluster.\n- The client should have the right to get the gateway connection stats on all requested gateways.", "operationId": "Gs_BatchGetGatewayConnectionStats", "responses": { "200": { diff --git a/api/ttn/lorawan/v3/gateway_services.proto b/api/ttn/lorawan/v3/gateway_services.proto index f8ab5d917d..0ec4098551 100644 --- a/api/ttn/lorawan/v3/gateway_services.proto +++ b/api/ttn/lorawan/v3/gateway_services.proto @@ -22,6 +22,7 @@ import "google/protobuf/field_mask.proto"; import "ttn/lorawan/v3/gateway.proto"; import "ttn/lorawan/v3/identifiers.proto"; import "ttn/lorawan/v3/rights.proto"; +import "validate/validate.proto"; option go_package = "go.thethings.network/lorawan-stack/v3/pkg/ttnpb"; @@ -94,7 +95,7 @@ service GatewayRegistry { } } -// The GatewayAcces service, exposed by the Identity Server, is used to manage +// The GatewayAccess service, exposed by the Identity Server, is used to manage // API keys and collaborators of gateways. service GatewayAccess { // List the rights the caller has on this gateway. @@ -163,3 +164,22 @@ message PullGatewayConfigurationRequest { service GatewayConfigurator { rpc PullConfiguration(PullGatewayConfigurationRequest) returns (stream Gateway); } + +message AssertGatewayRightsRequest { + repeated GatewayIdentifiers gateway_ids = 1 [ + (validate.rules).repeated.min_items = 1, + (validate.rules).repeated.max_items = 20 + ]; + Rights required = 2 [(validate.rules).message.required = true]; +} + +// The GatewayBatchAccess service, exposed by the Identity Server, is used to +// check the rights of the caller on multiple gateways at once. +// EXPERIMENTAL: This service is subject to change. +service GatewayBatchAccess { + // Assert that the caller has the requested rights on all the requested gateways. + // The check is successful if there are no errors. + rpc AssertRights(AssertGatewayRightsRequest) returns (google.protobuf.Empty) { + option (google.api.http) = {get: "/gateways/rights/batch"}; + } +} diff --git a/api/ttn/lorawan/v3/gatewayserver.proto b/api/ttn/lorawan/v3/gatewayserver.proto index 382afa4c0a..b11b083cf4 100644 --- a/api/ttn/lorawan/v3/gatewayserver.proto +++ b/api/ttn/lorawan/v3/gatewayserver.proto @@ -112,9 +112,10 @@ service Gs { } // Get statistics about gateway connections to the Gateway Server of a batch of gateways. - // This is not persisted between reconnects. - // Gateways that are not connected or are part of a different cluster are ignored. - // It is up to the client to make sure that the gateways are in the requested cluster. + // - Statistics are not persisted between reconnects. + // - Gateways that are not connected or are part of a different cluster are ignored. + // - The client should ensure that the requested gateways are in the requested cluster. + // - The client should have the right to get the gateway connection stats on all requested gateways. rpc BatchGetGatewayConnectionStats(BatchGetGatewayConnectionStatsRequest) returns (BatchGetGatewayConnectionStatsResponse) { option (google.api.http) = { post: "/gs/gateways/connection/stats" diff --git a/config/messages.json b/config/messages.json index 92457ed9f5..c7517d3898 100644 --- a/config/messages.json +++ b/config/messages.json @@ -5804,6 +5804,15 @@ "file": "application_registry.go" } }, + "error:pkg/identityserver:empty_request": { + "translations": { + "en": "empty request" + }, + "description": { + "package": "pkg/identityserver", + "file": "gateway_access.go" + } + }, "error:pkg/identityserver:end_device_euis_taken": { "translations": { "en": "an end device with JoinEUI `{join_eui}` and DevEUI `{dev_eui}` is already registered as `{device_id}` in application `{application_id}`" @@ -5840,6 +5849,15 @@ "file": "gateway_access.go" } }, + "error:pkg/identityserver:insufficient_rights": { + "translations": { + "en": "insufficient rights" + }, + "description": { + "package": "pkg/identityserver", + "file": "rights.go" + } + }, "error:pkg/identityserver:invalid_authorization": { "translations": { "en": "invalid authorization" @@ -5885,6 +5903,15 @@ "file": "user_access.go" } }, + "error:pkg/identityserver:neither_user_nor_organization": { + "translations": { + "en": "caller is neither a user nor an organization" + }, + "description": { + "package": "pkg/identityserver", + "file": "rights.go" + } + }, "error:pkg/identityserver:nested_organizations": { "translations": { "en": "organizations can not be nested" @@ -5939,6 +5966,15 @@ "file": "contact_info_registry.go" } }, + "error:pkg/identityserver:non_gateway_rights": { + "translations": { + "en": "non-gateway rights in request" + }, + "description": { + "package": "pkg/identityserver", + "file": "gateway_access.go" + } + }, "error:pkg/identityserver:oauth_client_rejected": { "translations": { "en": "OAuth client was rejected" @@ -6083,6 +6119,15 @@ "file": "registry_search.go" } }, + "error:pkg/identityserver:some_gateways_not_found": { + "translations": { + "en": "some gateways not found" + }, + "description": { + "package": "pkg/identityserver", + "file": "rights.go" + } + }, "error:pkg/identityserver:temporary_password_expired": { "translations": { "en": "temporary password expired" diff --git a/pkg/gatewayserver/gatewayserver_test.go b/pkg/gatewayserver/gatewayserver_test.go index 8659930fc9..e7e661fe87 100644 --- a/pkg/gatewayserver/gatewayserver_test.go +++ b/pkg/gatewayserver/gatewayserver_test.go @@ -2055,9 +2055,8 @@ func TestUpdateVersionInfo(t *testing.T) { //nolint:paralleltest time.Sleep(timeout) } -func TestBatchGetStatus(t *testing.T) { +func TestBatchGetStatus(t *testing.T) { // nolint:paralleltest a, ctx := test.New(t) - t.Parallel() for _, tc := range []struct { //nolint:paralleltest Name string @@ -2221,6 +2220,21 @@ func TestBatchGetStatus(t *testing.T) { is.GatewayRegistry().Add(ctx, gtwIDs2, registeredGatewayKey, mockGtw2, testRights...) time.Sleep(timeout) // Wait for setup to be completed. + // Invalid batch + res, err = statsClient.BatchGetGatewayConnectionStats( + statsCtx, + &ttnpb.BatchGetGatewayConnectionStatsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtwIDs1, + { + GatewayId: "unknown", + }, + }, + }, + ) + a.So(err, should.NotBeNil) + a.So(res, should.BeNil) + // Get Stats before connection. res, err = statsClient.BatchGetGatewayConnectionStats(statsCtx, request) a.So(err, should.BeNil) diff --git a/pkg/gatewayserver/grpc.go b/pkg/gatewayserver/grpc.go index 03a6cbaef4..5a971abfc3 100644 --- a/pkg/gatewayserver/grpc.go +++ b/pkg/gatewayserver/grpc.go @@ -20,7 +20,6 @@ import ( "go.thethings.network/lorawan-stack/v3/pkg/errors" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" "go.thethings.network/lorawan-stack/v3/pkg/unique" - "golang.org/x/sync/errgroup" ) // GetGatewayConnectionStats returns statistics about a gateway connection. @@ -60,22 +59,16 @@ func applyGatewayConnectionStatsFieldMask( return dst, dst.SetFields(src, paths...) } -// BatchGetGatewayConnectionStats gets statistics about gateway connections to the Gateway Server -// of a batch of gateways. -// This RPC skips unconnected gateways. -// FieldMask paths can be used directly since they are sanitized by the middleware. +// BatchGetGatewayConnectionStats implements Gs. func (gs *GatewayServer) BatchGetGatewayConnectionStats( ctx context.Context, req *ttnpb.BatchGetGatewayConnectionStatsRequest, ) (*ttnpb.BatchGetGatewayConnectionStatsResponse, error) { - wg, wgCtx := errgroup.WithContext(ctx) - for _, ids := range req.GatewayIds { - ids := ids - wg.Go(func() error { - return gs.entityRegistry.AssertGatewayRights(wgCtx, ids, ttnpb.Right_RIGHT_GATEWAY_STATUS_READ) - }) - } - if err := wg.Wait(); err != nil { + if err := gs.entityRegistry.AssertGatewayBatchRights( + ctx, + req.GatewayIds, + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + ); err != nil { return nil, err } diff --git a/pkg/gatewayserver/is.go b/pkg/gatewayserver/is.go index c9325eb57b..89169f8068 100644 --- a/pkg/gatewayserver/is.go +++ b/pkg/gatewayserver/is.go @@ -44,6 +44,34 @@ func NewIS(c Cluster) *IS { } } +// AssertGatewayBatchRights implements EntityRegistry. +func (is IS) AssertGatewayBatchRights( + ctx context.Context, + ids []*ttnpb.GatewayIdentifiers, + required ...ttnpb.Right, +) error { + ctx, err := getAuthenticatedContext(ctx) + if err != nil { + return err + } + + client, err := is.newGatewayBatchAccessClient(ctx) + if err != nil { + return err + } + callOpt, err := rpcmetadata.WithForwardedAuth(ctx, is.AllowInsecureForCredentials()) + if err != nil { + return err + } + _, err = client.AssertRights(ctx, &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: ids, + Required: &ttnpb.Rights{ + Rights: required, + }, + }, callOpt) + return err +} + // AssertGatewayRights implements EntityRegistry. func (is IS) AssertGatewayRights(ctx context.Context, ids *ttnpb.GatewayIdentifiers, required ...ttnpb.Right) error { ctx, err := getAuthenticatedContext(ctx) @@ -157,6 +185,14 @@ func (is IS) newRegistryClient(ctx context.Context, ids *ttnpb.GatewayIdentifier return ttnpb.NewGatewayRegistryClient(cc), nil } +func (is IS) newGatewayBatchAccessClient(ctx context.Context) (ttnpb.GatewayBatchAccessClient, error) { + cc, err := is.GetPeerConn(ctx, ttnpb.ClusterRole_ENTITY_REGISTRY, nil) + if err != nil { + return nil, err + } + return ttnpb.NewGatewayBatchAccessClient(cc), nil +} + func getAuthenticatedContext(ctx context.Context) (context.Context, error) { return ctx, nil } diff --git a/pkg/gatewayserver/registry.go b/pkg/gatewayserver/registry.go index 48d8343f05..b96dad941e 100644 --- a/pkg/gatewayserver/registry.go +++ b/pkg/gatewayserver/registry.go @@ -45,6 +45,8 @@ type GatewayConnectionStatsRegistry interface { type EntityRegistry interface { // AssertGatewayRights checks whether the gateway authentication (provied in the context) contains the required rights. AssertGatewayRights(ctx context.Context, ids *ttnpb.GatewayIdentifiers, required ...ttnpb.Right) error + // AssertGatewayBatchRights checks whether the caller has the requested rights on all the requested gateways. + AssertGatewayBatchRights(ctx context.Context, ids []*ttnpb.GatewayIdentifiers, required ...ttnpb.Right) error // Get the identifiers of the gateway that has the given EUI registered. GetIdentifiersForEUI(ctx context.Context, in *ttnpb.GetGatewayIdentifiersForEUIRequest) (*ttnpb.GatewayIdentifiers, error) // Get the gateway with the given identifiers, selecting the fields specified. diff --git a/pkg/identityserver/gateway_access.go b/pkg/identityserver/gateway_access.go index 97fd144957..076fce916e 100644 --- a/pkg/identityserver/gateway_access.go +++ b/pkg/identityserver/gateway_access.go @@ -400,3 +400,44 @@ func (ga *gatewayAccess) SetCollaborator(ctx context.Context, req *ttnpb.SetGate func (ga *gatewayAccess) ListCollaborators(ctx context.Context, req *ttnpb.ListGatewayCollaboratorsRequest) (*ttnpb.Collaborators, error) { return ga.listGatewayCollaborators(ctx, req) } + +type gatewayBatchAccess struct { + ttnpb.UnimplementedGatewayBatchAccessServer + + *IdentityServer +} + +var ( + errEmtpyRequest = errors.DefineInvalidArgument( + "empty_request", + "empty request", + ) + errNonGatewayRights = errors.DefineInvalidArgument( + "non_gateway_rights", + "non-gateway rights in request", + ) +) + +// AssertRights implements ttnpb.GatewayBatchAccessServer. +func (gba *gatewayBatchAccess) AssertRights( + ctx context.Context, + req *ttnpb.AssertGatewayRightsRequest, +) (*emptypb.Empty, error) { + // Sanitize request. + required := req.Required.Unique() + if len(required.GetRights()) == 0 { + return nil, errEmtpyRequest.New() + } + + // Check that the request is checking only gateway rights. + if !ttnpb.AllGatewayRights.IncludesAll(required.GetRights()...) { + return nil, errNonGatewayRights.New() + } + + err := gba.assertGatewayRights(ctx, req.GatewayIds, required) + if err != nil { + return nil, err + } + + return ttnpb.Empty, nil +} diff --git a/pkg/identityserver/gateway_access_test.go b/pkg/identityserver/gateway_access_test.go index 498b4c3908..533ac26f1a 100644 --- a/pkg/identityserver/gateway_access_test.go +++ b/pkg/identityserver/gateway_access_test.go @@ -465,3 +465,437 @@ func TestGatewayAccessClusterAuth(t *testing.T) { } }, withPrivateTestDatabase(p)) } + +func TestGatewayBatchAccess(t *testing.T) { + p := &storetest.Population{} + + t.Parallel() + a, ctx := test.New(t) + + admin := p.NewUser() + admin.Admin = true + adminKey, _ := p.NewAPIKey(admin.GetEntityIdentifiers(), ttnpb.Right_RIGHT_ALL) + adminCreds := rpcCreds(adminKey) + + usr1 := p.NewUser() + usr1Key, _ := p.NewAPIKey(usr1.GetEntityIdentifiers(), ttnpb.Right_RIGHT_ALL) + usr1Creds := rpcCreds(usr1Key) + + gtw1 := p.NewGateway(usr1.GetOrganizationOrUserIdentifiers()) + gtw1.StatusPublic = true + gtw1.LocationPublic = true + gtw1Key, _ := p.NewAPIKey(gtw1.GetEntityIdentifiers(), ttnpb.Right_RIGHT_ALL) + gtw1Creds := rpcCreds(gtw1Key) + + gtw2 := p.NewGateway(usr1.GetOrganizationOrUserIdentifiers()) + + limitedKey, _ := p.NewAPIKey(usr1.GetEntityIdentifiers(), + ttnpb.Right_RIGHT_GATEWAY_INFO, + ttnpb.Right_RIGHT_GATEWAY_SETTINGS_BASIC, + ttnpb.Right_RIGHT_GATEWAY_SETTINGS_COLLABORATORS, + ) + limitedCreds := rpcCreds(limitedKey) + + collaboratorWithLimitedRights := p.NewUser() + p.NewMembership( + collaboratorWithLimitedRights.GetOrganizationOrUserIdentifiers(), + gtw2.GetEntityIdentifiers(), + ttnpb.Right_RIGHT_GATEWAY_TRAFFIC_READ, + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + ) + collaboratorKey, _ := p.NewAPIKey( + collaboratorWithLimitedRights.GetEntityIdentifiers(), + ttnpb.Right_RIGHT_ALL, + ) + collaboratorCreds := rpcCreds(collaboratorKey) + + usr2 := p.NewUser() + p.NewMembership( + usr2.GetOrganizationOrUserIdentifiers(), + gtw1.GetEntityIdentifiers(), + ttnpb.Right_RIGHT_GATEWAY_INFO, + ttnpb.Right_RIGHT_GATEWAY_TRAFFIC_READ, + ) + + gtw3 := p.NewGateway(usr2.GetOrganizationOrUserIdentifiers()) + + randomUser := p.NewUser() + randomUserKey, _ := p.NewAPIKey(randomUser.GetEntityIdentifiers(), ttnpb.Right_RIGHT_ALL) + randomUserCreds := rpcCreds(randomUserKey) + + statsUser := p.NewUser() + statsUserKey, _ := p.NewAPIKey(statsUser.GetEntityIdentifiers(), ttnpb.Right_RIGHT_ALL) + statsUserCreds := rpcCreds(statsUserKey) + + locUser := p.NewUser() + locUserKey, _ := p.NewAPIKey(locUser.GetEntityIdentifiers(), ttnpb.Right_RIGHT_ALL) + locUserCreds := rpcCreds(locUserKey) + + testWithIdentityServer(t, func(is *IdentityServer, cc *grpc.ClientConn) { + is.config.AdminRights.All = true + + reg := ttnpb.NewGatewayBatchAccessClient(cc) + + for _, tc := range []struct { + Name string + Credentials grpc.CallOption + Request *ttnpb.AssertGatewayRightsRequest + ErrorAssertion func(error) bool + }{ + { + Name: "Empty request", + Credentials: usr1Creds, + Request: &ttnpb.AssertGatewayRightsRequest{}, + ErrorAssertion: errors.IsInvalidArgument, + }, + { + Name: "No rights requested", + Credentials: usr1Creds, + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{}, + }, + }, + ErrorAssertion: errors.IsInvalidArgument, + }, + { + Name: "No gateway rights", + Credentials: usr1Creds, + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_APPLICATION_ALL, + }, + }, + }, + ErrorAssertion: errors.IsInvalidArgument, + }, + { + Name: "Extra rights requested", + Credentials: usr1Creds, + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_APPLICATION_ALL, + ttnpb.Right_RIGHT_GATEWAY_ALL, + }, + }, + }, + ErrorAssertion: errors.IsInvalidArgument, + }, + { + Name: "Not user or organization", + Credentials: gtw1Creds, + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_ALL, + }, + }, + }, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "Limited rights", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_ALL, + }, + }, + }, + Credentials: limitedCreds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "Random user", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_ALL, + }, + }, + }, + Credentials: randomUserCreds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "One gateway not found", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + { + GatewayId: "unknown", + }, + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_ALL, + }, + }, + }, + Credentials: usr1Creds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "One gateway not found for admin", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + { + GatewayId: "unknown", + }, + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_ALL, + }, + }, + }, + Credentials: adminCreds, + ErrorAssertion: errors.IsNotFound, + }, + { + Name: "No rights for one gateway", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + gtw3.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_ALL, + }, + }, + }, + Credentials: usr1Creds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "Collaborator with limited rights", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_SETTINGS_API_KEYS, + }, + }, + }, + Credentials: collaboratorCreds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "Limited rights for admin", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_SETTINGS_API_KEYS, + }, + }, + }, + Credentials: adminCreds, + ErrorAssertion: errors.IsNotFound, + }, + { + Name: "Read Stats Failure", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + }, + }, + }, + Credentials: statsUserCreds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "Read Location Failure", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw3.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_LOCATION_READ, + }, + }, + }, + Credentials: locUserCreds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "Collaborator with insufficient rights for one gateway", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + ttnpb.Right_RIGHT_GATEWAY_TRAFFIC_READ, + }, + }, + }, + Credentials: collaboratorCreds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "Collaborator with excessive rights requested", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + ttnpb.Right_RIGHT_GATEWAY_TRAFFIC_READ, + ttnpb.Right_RIGHT_GATEWAY_SETTINGS_API_KEYS, + }, + }, + }, + Credentials: collaboratorCreds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "Not allowed to read private stats", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + }, + }, + }, + Credentials: statsUserCreds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "Not allowed to read private location", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_LOCATION_READ, + }, + }, + }, + Credentials: locUserCreds, + ErrorAssertion: errors.IsPermissionDenied, + }, + { + Name: "Read Public Stats", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + }, + }, + }, + Credentials: statsUserCreds, + }, + { + Name: "Read Public Location", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_LOCATION_READ, + }, + }, + }, + Credentials: locUserCreds, + }, + { + Name: "Valid collaborator", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + ttnpb.Right_RIGHT_GATEWAY_TRAFFIC_READ, + }, + }, + }, + Credentials: collaboratorCreds, + }, + { + Name: "Valid request", + Request: &ttnpb.AssertGatewayRightsRequest{ + GatewayIds: []*ttnpb.GatewayIdentifiers{ + gtw1.GetIds(), + gtw2.GetIds(), + }, + Required: &ttnpb.Rights{ + Rights: []ttnpb.Right{ + ttnpb.Right_RIGHT_GATEWAY_SETTINGS_API_KEYS, + ttnpb.Right_RIGHT_GATEWAY_TRAFFIC_DOWN_WRITE, + ttnpb.Right_RIGHT_GATEWAY_WRITE_SECRETS, + }, + }, + }, + Credentials: usr1Creds, + }, + } { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + _, err := reg.AssertRights(ctx, tc.Request, tc.Credentials) + if err != nil { + if tc.ErrorAssertion == nil || !a.So(tc.ErrorAssertion(err), should.BeTrue) { + t.Fatalf("Unexpected error: %v", err) + } + } else if tc.ErrorAssertion != nil { + t.Fatalf("Expected error") + } + }) + } + }, withPrivateTestDatabase(p)) +} diff --git a/pkg/identityserver/identityserver.go b/pkg/identityserver/identityserver.go index 172446cbe9..5c559dfee4 100644 --- a/pkg/identityserver/identityserver.go +++ b/pkg/identityserver/identityserver.go @@ -197,6 +197,7 @@ func New(c *component.Component, config *Config) (is *IdentityServer, err error) "/ttn.lorawan.v3.EndDeviceBatchRegistry", "/ttn.lorawan.v3.GatewayRegistry", "/ttn.lorawan.v3.GatewayAccess", + "/ttn.lorawan.v3.GatewayBatchAccess", "/ttn.lorawan.v3.OrganizationRegistry", "/ttn.lorawan.v3.OrganizationAccess", "/ttn.lorawan.v3.UserRegistry", @@ -244,6 +245,7 @@ func (is *IdentityServer) RegisterServices(s *grpc.Server) { ttnpb.RegisterEndDeviceRegistryServer(s, &endDeviceRegistry{IdentityServer: is}) ttnpb.RegisterGatewayRegistryServer(s, &gatewayRegistry{IdentityServer: is}) ttnpb.RegisterGatewayAccessServer(s, &gatewayAccess{IdentityServer: is}) + ttnpb.RegisterGatewayBatchAccessServer(s, &gatewayBatchAccess{IdentityServer: is}) ttnpb.RegisterOrganizationRegistryServer(s, &organizationRegistry{IdentityServer: is}) ttnpb.RegisterOrganizationAccessServer(s, &organizationAccess{IdentityServer: is}) ttnpb.RegisterUserRegistryServer(s, &userRegistry{IdentityServer: is}) @@ -269,6 +271,7 @@ func (is *IdentityServer) RegisterHandlers(s *runtime.ServeMux, conn *grpc.Clien ttnpb.RegisterEndDeviceRegistryHandler(is.Context(), s, conn) ttnpb.RegisterGatewayRegistryHandler(is.Context(), s, conn) ttnpb.RegisterGatewayAccessHandler(is.Context(), s, conn) + ttnpb.RegisterGatewayBatchAccessHandler(is.Context(), s, conn) // nolint:errcheck ttnpb.RegisterOrganizationRegistryHandler(is.Context(), s, conn) ttnpb.RegisterOrganizationAccessHandler(is.Context(), s, conn) ttnpb.RegisterUserRegistryHandler(is.Context(), s, conn) @@ -289,7 +292,7 @@ func (is *IdentityServer) RegisterInterop(srv *interop.Server) { } // Roles returns the roles that the Identity Server fulfills. -func (is *IdentityServer) Roles() []ttnpb.ClusterRole { +func (*IdentityServer) Roles() []ttnpb.ClusterRole { return []ttnpb.ClusterRole{ttnpb.ClusterRole_ACCESS, ttnpb.ClusterRole_ENTITY_REGISTRY} } @@ -297,6 +300,7 @@ var softDeleteFieldMask = []string{"deleted_at"} var errRestoreWindowExpired = errors.DefineFailedPrecondition("restore_window_expired", "this entity can no longer be restored") +// Close closes the Identity Server database connections and the underlying component. func (is *IdentityServer) Close() { is.db.Close() is.Component.Close() diff --git a/pkg/identityserver/mock/gateway_registry.go b/pkg/identityserver/mock/gateway_registry.go index eec0350305..2f6e916a44 100644 --- a/pkg/identityserver/mock/gateway_registry.go +++ b/pkg/identityserver/mock/gateway_registry.go @@ -17,9 +17,11 @@ package mockis import ( "context" "fmt" + "strings" "sync" "go.thethings.network/lorawan-stack/v3/pkg/errors" + "go.thethings.network/lorawan-stack/v3/pkg/rpcmetadata" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" "go.thethings.network/lorawan-stack/v3/pkg/types" "go.thethings.network/lorawan-stack/v3/pkg/unique" @@ -52,6 +54,7 @@ func DefaultGateway(ids *ttnpb.GatewayIdentifiers, locationPublic, updateLocatio type mockISGatewayRegistry struct { ttnpb.UnimplementedGatewayRegistryServer ttnpb.UnimplementedGatewayAccessServer + ttnpb.UnimplementedGatewayBatchAccessServer gateways map[string]*ttnpb.Gateway gatewayAuths map[string][]string gatewayRights map[string]authKeyToRights @@ -165,6 +168,49 @@ func (is *mockISGatewayRegistry) ListRights( return res, err } +// AssertRights implements GatewayBatchAccess. +func (is *mockISGatewayRegistry) AssertRights( + ctx context.Context, + req *ttnpb.AssertGatewayRightsRequest, +) (*emptypb.Empty, error) { + is.mu.Lock() + defer is.mu.Unlock() + + md := rpcmetadata.FromIncomingContext(ctx) + if md.AuthType == "" { + return nil, errNoGatewayRights.New() + } + if strings.ToLower(md.AuthType) != "bearer" { + return nil, errNoGatewayRights.New() + } + + for _, ids := range req.GatewayIds { + uid := unique.ID(ctx, ids) + auths, ok := is.gatewayAuths[uid] + if !ok { + return nil, errNoGatewayRights.New() + } + for _, a := range auths { + if a != fmt.Sprintf("Bearer %s", md.AuthValue) { + return nil, errNoGatewayRights.New() + } + authKeyToRights := is.gatewayRights[uid] + if authKeyToRights == nil { + return nil, errNoGatewayRights.New() + } + r, ok := authKeyToRights[a] + if !ok { + return nil, errNoGatewayRights.New() + } + rights := &ttnpb.Rights{Rights: r} + if len(rights.Intersect(req.Required).GetRights()) == 0 { + return nil, errNoGatewayRights.New() + } + } + } + return ttnpb.Empty, nil +} + func (is *mockISGatewayRegistry) GetIdentifiersForEUI( _ context.Context, req *ttnpb.GetGatewayIdentifiersForEUIRequest, diff --git a/pkg/identityserver/mock/mock.go b/pkg/identityserver/mock/mock.go index 54e849c6a0..addd7f5430 100644 --- a/pkg/identityserver/mock/mock.go +++ b/pkg/identityserver/mock/mock.go @@ -68,6 +68,7 @@ func New(ctx context.Context) (*MockDefinition, string, closeMock) { ttnpb.RegisterGatewayRegistryServer(srv.Server, is.gatewayRegistry) ttnpb.RegisterGatewayAccessServer(srv.Server, is.gatewayRegistry) + ttnpb.RegisterGatewayBatchAccessServer(srv.Server, is.gatewayRegistry) ttnpb.RegisterEndDeviceRegistryServer(srv.Server, is.endDeviceRegistry) diff --git a/pkg/identityserver/rights.go b/pkg/identityserver/rights.go index e3ce27a417..6850da8e7b 100644 --- a/pkg/identityserver/rights.go +++ b/pkg/identityserver/rights.go @@ -20,6 +20,7 @@ import ( "go.thethings.network/lorawan-stack/v3/pkg/errors" "go.thethings.network/lorawan-stack/v3/pkg/identityserver/store" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" + "go.thethings.network/lorawan-stack/v3/pkg/unique" ) func allPotentialRights(eIDs *ttnpb.EntityIdentifiers, rights *ttnpb.Rights) *ttnpb.Rights { @@ -213,3 +214,130 @@ func (is *IdentityServer) UserRights(ctx context.Context, userIDs *ttnpb.UserIde } return entity.Union(universal), nil } + +var ( + errInsufficientRights = errors.DefinePermissionDenied( + "insufficient_rights", + "insufficient rights", + ) + errNeitherUserNorOrganization = errors.DefinePermissionDenied( + "neither_user_nor_organization", + "caller is neither a user nor an organization", + ) + errSomeGatewaysNotFound = errors.DefineNotFound( + "some_gateways_not_found", + "some gateways not found", + ) +) + +func (is *IdentityServer) assertGatewayRights( // nolint:gocyclo + ctx context.Context, + gtwIDs []*ttnpb.GatewayIdentifiers, + requiredGatewayRights *ttnpb.Rights, +) error { + authInfo, err := is.authInfo(ctx) + if err != nil { + return err + } + + // If the caller is not an organization or user, there's nothing more to do. + ouID := authInfo.GetOrganizationOrUserIdentifiers() + if ouID == nil { + return errNeitherUserNorOrganization.New() + } + + // Check that the caller has the requested rights. + authInfoRights := ttnpb.RightsFrom(authInfo.GetRights()...) + if !authInfoRights.IncludesAll(requiredGatewayRights.GetRights()...) { + return errInsufficientRights.New() + } + + return is.store.Transact(ctx, func(ctx context.Context, st store.Store) error { + gtws, err := st.FindGateways(ctx, gtwIDs, []string{"ids", "status_public", "location_public"}) + if err != nil { + return err + } + if len(gtws) != len(gtwIDs) { + if is.IsAdmin(ctx) { + // Return the cause only to the admin. + // This follows the same logic as in ListRights. + return errSomeGatewaysNotFound.New() + } + return errInsufficientRights.New() + } + + // Filter out the public gateways from membership checks, + // when only public fields are requested. + publicGatewayIds := make(map[string]struct{}, len(gtws)) + bothStatsAndLocation := len(requiredGatewayRights.Sub( + ttnpb.RightsFrom( + ttnpb.Right_RIGHT_GATEWAY_LOCATION_READ, + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + ), + ).GetRights()) == 0 + onlyPublicLocation := len(requiredGatewayRights.Sub( + ttnpb.RightsFrom( + ttnpb.Right_RIGHT_GATEWAY_LOCATION_READ, + ), + ).GetRights()) == 0 + onlyPublicStats := len(requiredGatewayRights.Sub( + ttnpb.RightsFrom( + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + ), + ).GetRights()) == 0 + + if bothStatsAndLocation { + for _, gtw := range gtws { + if gtw.StatusPublic && gtw.LocationPublic { + publicGatewayIds[unique.ID(ctx, gtw.Ids)] = struct{}{} + } + } + } else { + if onlyPublicStats { + for _, gtw := range gtws { + if gtw.StatusPublic { + publicGatewayIds[unique.ID(ctx, gtw.Ids)] = struct{}{} + } + } + } + if onlyPublicLocation { + for _, gtw := range gtws { + if gtw.LocationPublic { + publicGatewayIds[unique.ID(ctx, gtw.Ids)] = struct{}{} + } + } + } + } + + entityIDs := make([]string, 0, len(gtwIDs)) + for _, gtwID := range gtwIDs { + if _, ok := publicGatewayIds[unique.ID(ctx, gtwID)]; ok { + continue + } + entityIDs = append(entityIDs, gtwID.GetEntityIdentifiers().IDString()) + } + membershipChains, err := st.FindAccountMembershipChains( + ctx, + ouID, + store.EntityGateway, + entityIDs..., + ) + if err != nil { + return err + } + if len(membershipChains) != len(entityIDs) { + // Some memberships were not found. + if is.IsAdmin(ctx) { + return errSomeGatewaysNotFound.New() + } + return errInsufficientRights.New() + } + for _, chain := range membershipChains { + // Make sure that there are no extra rights requested. + if !chain.GetRights().IncludesAll(requiredGatewayRights.GetRights()...) { + return errInsufficientRights.New() + } + } + return nil + }) +} diff --git a/pkg/ttnpb/gateway_services.pb.go b/pkg/ttnpb/gateway_services.pb.go index 69dd7dcd32..8c43a8bf4f 100644 --- a/pkg/ttnpb/gateway_services.pb.go +++ b/pkg/ttnpb/gateway_services.pb.go @@ -21,6 +21,7 @@ package ttnpb import ( + _ "github.com/envoyproxy/protoc-gen-validate/validate" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -92,6 +93,61 @@ func (x *PullGatewayConfigurationRequest) GetFieldMask() *fieldmaskpb.FieldMask return nil } +type AssertGatewayRightsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + GatewayIds []*GatewayIdentifiers `protobuf:"bytes,1,rep,name=gateway_ids,json=gatewayIds,proto3" json:"gateway_ids,omitempty"` + Required *Rights `protobuf:"bytes,2,opt,name=required,proto3" json:"required,omitempty"` +} + +func (x *AssertGatewayRightsRequest) Reset() { + *x = AssertGatewayRightsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ttn_lorawan_v3_gateway_services_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AssertGatewayRightsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssertGatewayRightsRequest) ProtoMessage() {} + +func (x *AssertGatewayRightsRequest) ProtoReflect() protoreflect.Message { + mi := &file_ttn_lorawan_v3_gateway_services_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssertGatewayRightsRequest.ProtoReflect.Descriptor instead. +func (*AssertGatewayRightsRequest) Descriptor() ([]byte, []int) { + return file_ttn_lorawan_v3_gateway_services_proto_rawDescGZIP(), []int{1} +} + +func (x *AssertGatewayRightsRequest) GetGatewayIds() []*GatewayIdentifiers { + if x != nil { + return x.GatewayIds + } + return nil +} + +func (x *AssertGatewayRightsRequest) GetRequired() *Rights { + if x != nil { + return x.Required + } + return nil +} + var File_ttn_lorawan_v3_gateway_services_proto protoreflect.FileDescriptor var file_ttn_lorawan_v3_gateway_services_proto_rawDesc = []byte{ @@ -110,181 +166,202 @@ var file_ttn_lorawan_v3_gateway_services_proto_rawDesc = []byte{ 0x76, 0x33, 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x74, 0x74, 0x6e, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2f, 0x76, 0x33, 0x2f, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0xa1, 0x01, 0x0a, 0x1f, 0x50, 0x75, 0x6c, 0x6c, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x74, 0x6e, - 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x52, 0x0a, - 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, - 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x32, 0xd9, 0x08, 0x0a, 0x0f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x12, 0xd3, 0x01, 0x0a, 0x06, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, - 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x74, 0x6e, - 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x22, 0x89, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x82, 0x01, 0x3a, 0x01, 0x2a, - 0x5a, 0x4c, 0x3a, 0x01, 0x2a, 0x22, 0x47, 0x2f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x22, 0x2f, - 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, - 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, - 0x6d, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x21, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, - 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x74, 0x6e, 0x2e, - 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, 0x67, 0x61, 0x74, - 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, - 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x6e, - 0x0a, 0x14, 0x47, 0x65, 0x74, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, - 0x46, 0x6f, 0x72, 0x45, 0x55, 0x49, 0x12, 0x32, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, - 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x46, 0x6f, 0x72, - 0x45, 0x55, 0x49, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x74, 0x6e, - 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0xd8, - 0x01, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x23, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, - 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, - 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x74, - 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x22, 0x90, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x89, 0x01, - 0x5a, 0x31, 0x12, 0x2f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, - 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x73, 0x5a, 0x49, 0x12, 0x47, 0x2f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0x09, - 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0x76, 0x0a, 0x06, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, - 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x74, 0x6e, 0x2e, - 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x1a, 0x22, 0x2f, - 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x2e, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, - 0x7d, 0x12, 0x64, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x74, 0x74, - 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, - 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, - 0x16, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x6d, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x6f, - 0x72, 0x65, 0x12, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, + 0x6f, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa1, 0x01, 0x0a, 0x1f, 0x50, + 0x75, 0x6c, 0x6c, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x43, + 0x0a, 0x0b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, + 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x52, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, + 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, + 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0xab, + 0x01, 0x0a, 0x1a, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x52, 0x69, 0x67, 0x68, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, + 0x0b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x26, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x22, 0x1e, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, - 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x69, 0x0a, 0x05, 0x50, 0x75, 0x72, 0x67, 0x65, 0x12, - 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, - 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x24, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1e, 0x2a, 0x1c, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, - 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x75, 0x72, 0x67, - 0x65, 0x32, 0xb3, 0x0a, 0x0a, 0x0d, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x41, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x12, 0x6f, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x69, 0x67, 0x68, 0x74, - 0x73, 0x12, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x92, 0x01, 0x04, 0x08, 0x01, + 0x10, 0x14, 0x52, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x73, 0x12, 0x3c, + 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, + 0x33, 0x2e, 0x52, 0x69, 0x67, 0x68, 0x74, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, + 0x10, 0x01, 0x52, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x32, 0xd9, 0x08, 0x0a, + 0x0f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, + 0x12, 0xd3, 0x01, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x74, 0x74, + 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, + 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x22, 0x89, 0x01, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x82, 0x01, 0x3a, 0x01, 0x2a, 0x5a, 0x4c, 0x3a, 0x01, 0x2a, 0x22, 0x47, 0x2f, 0x6f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x63, 0x6f, + 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x6f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x73, 0x22, 0x2f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x63, + 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x67, 0x61, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0x6d, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x21, 0x2e, + 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, + 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, + 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x24, 0x12, 0x22, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x6e, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x55, 0x49, 0x12, 0x32, 0x2e, + 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, + 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x55, 0x49, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, - 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x52, 0x69, 0x67, 0x68, 0x74, 0x73, 0x22, 0x25, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, - 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x69, - 0x67, 0x68, 0x74, 0x73, 0x12, 0x8a, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, - 0x50, 0x49, 0x4b, 0x65, 0x79, 0x12, 0x2a, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, - 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, - 0x65, 0x77, 0x61, 0x79, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, - 0x76, 0x33, 0x2e, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x30, 0x3a, 0x01, 0x2a, 0x22, 0x2b, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, + 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0xd8, 0x01, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x23, + 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, + 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x22, 0x90, 0x01, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x89, 0x01, 0x5a, 0x31, 0x12, 0x2f, 0x2f, 0x75, 0x73, 0x65, 0x72, + 0x73, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x7d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x5a, 0x49, 0x12, 0x47, 0x2f, 0x6f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x63, 0x6f, + 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x6f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0x09, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, + 0x12, 0x76, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x74, 0x74, 0x6e, + 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, + 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x27, 0x3a, 0x01, 0x2a, 0x1a, 0x22, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, + 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x64, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x12, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, + 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1e, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x6d, + 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, + 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x22, 0x1e, 0x2f, + 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x69, 0x0a, + 0x05, 0x50, 0x75, 0x72, 0x67, 0x65, 0x12, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, + 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x2a, 0x1c, 0x2f, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, + 0x64, 0x7d, 0x2f, 0x70, 0x75, 0x72, 0x67, 0x65, 0x32, 0xb3, 0x0a, 0x0a, 0x0d, 0x47, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x6f, 0x0a, 0x0a, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x69, 0x67, 0x68, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, 0x74, + 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x52, 0x69, + 0x67, 0x68, 0x74, 0x73, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x67, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x12, 0x8a, 0x01, 0x0a, 0x0c, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x12, 0x2a, 0x2e, 0x74, + 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x41, 0x50, 0x49, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, + 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x3a, 0x01, 0x2a, 0x22, 0x2b, 0x2f, 0x67, 0x61, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, + 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, + 0x61, 0x70, 0x69, 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x86, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x29, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, + 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x33, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x2d, 0x12, 0x2b, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x6b, 0x65, 0x79, - 0x73, 0x12, 0x86, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, - 0x73, 0x12, 0x29, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, - 0x76, 0x33, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x41, 0x50, - 0x49, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, - 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x50, - 0x49, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x12, 0x2b, 0x2f, - 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, - 0x7d, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x8a, 0x01, 0x0a, 0x09, 0x47, - 0x65, 0x74, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x12, 0x27, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, - 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, - 0x65, 0x77, 0x61, 0x79, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, - 0x76, 0x33, 0x2e, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x22, 0x3c, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x36, 0x12, 0x34, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x2f, 0x7b, - 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x97, 0x01, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x12, 0x2a, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, - 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, - 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x22, 0x43, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x3d, 0x3a, 0x01, 0x2a, 0x1a, 0x38, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x2e, - 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x61, 0x70, 0x69, 0x2d, - 0x6b, 0x65, 0x79, 0x73, 0x2f, 0x7b, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x69, 0x64, - 0x7d, 0x12, 0xbb, 0x02, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2d, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, - 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, - 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcf, 0x01, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0xc8, 0x01, 0x5a, 0x56, 0x12, 0x54, 0x2f, 0x67, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, - 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x63, 0x6f, - 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, - 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x5a, - 0x6e, 0x12, 0x6c, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x2f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, - 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x6f, - 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x12, - 0x95, 0x01, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x12, 0x2d, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, - 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, - 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3b, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x35, 0x3a, 0x01, 0x2a, 0x1a, 0x30, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, - 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, - 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x9d, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x2f, 0x2e, - 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, - 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, - 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x38, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x32, 0x12, 0x30, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, - 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, - 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x32, 0x76, 0x0a, 0x13, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x5f, - 0x0a, 0x11, 0x50, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, - 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, - 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x30, 0x01, 0x42, - 0x31, 0x5a, 0x2f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x74, 0x6e, - 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x12, 0x8a, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x12, + 0x27, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, + 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x41, 0x50, 0x49, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, + 0x22, 0x3c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x36, 0x12, 0x34, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, + 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x61, 0x70, 0x69, + 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x2f, 0x7b, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x97, + 0x01, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x12, + 0x2a, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x41, 0x50, + 0x49, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x74, 0x74, + 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x50, 0x49, + 0x4b, 0x65, 0x79, 0x22, 0x43, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3d, 0x3a, 0x01, 0x2a, 0x1a, 0x38, + 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, + 0x64, 0x7d, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x2f, 0x7b, 0x61, 0x70, 0x69, + 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x69, 0x64, 0x7d, 0x12, 0xbb, 0x02, 0x0a, 0x0f, 0x47, 0x65, 0x74, + 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2d, 0x2e, 0x74, + 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, + 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x74, 0x74, + 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, + 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcf, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0xc8, 0x01, 0x5a, 0x56, + 0x12, 0x54, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x5a, 0x6e, 0x12, 0x6c, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, + 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x63, 0x6f, 0x6c, + 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x7b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x95, 0x01, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x43, 0x6f, + 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2d, 0x2e, 0x74, 0x74, 0x6e, + 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x65, 0x74, 0x47, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x3a, 0x01, 0x2a, 0x1a, 0x30, 0x2f, 0x67, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, + 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x9d, + 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x73, 0x12, 0x2f, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, + 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, + 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x73, 0x22, 0x38, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x32, 0x12, 0x30, 0x2f, 0x67, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, + 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x32, 0x76, + 0x0a, 0x13, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x5f, 0x0a, 0x11, 0x50, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x2e, 0x74, 0x74, 0x6e, + 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x50, 0x75, 0x6c, 0x6c, + 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x74, + 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x30, 0x01, 0x32, 0x88, 0x01, 0x0a, 0x12, 0x47, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x42, 0x61, 0x74, 0x63, 0x68, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x72, 0x0a, + 0x0c, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x52, 0x69, 0x67, 0x68, 0x74, 0x73, 0x12, 0x2a, 0x2e, + 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, + 0x73, 0x73, 0x65, 0x72, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x69, 0x67, 0x68, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x67, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x73, 0x2f, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2f, 0x62, 0x61, 0x74, 0x63, + 0x68, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, + 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, + 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, + 0x74, 0x6e, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -299,74 +376,79 @@ func file_ttn_lorawan_v3_gateway_services_proto_rawDescGZIP() []byte { return file_ttn_lorawan_v3_gateway_services_proto_rawDescData } -var file_ttn_lorawan_v3_gateway_services_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_ttn_lorawan_v3_gateway_services_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_ttn_lorawan_v3_gateway_services_proto_goTypes = []interface{}{ (*PullGatewayConfigurationRequest)(nil), // 0: ttn.lorawan.v3.PullGatewayConfigurationRequest - (*GatewayIdentifiers)(nil), // 1: ttn.lorawan.v3.GatewayIdentifiers - (*fieldmaskpb.FieldMask)(nil), // 2: google.protobuf.FieldMask - (*CreateGatewayRequest)(nil), // 3: ttn.lorawan.v3.CreateGatewayRequest - (*GetGatewayRequest)(nil), // 4: ttn.lorawan.v3.GetGatewayRequest - (*GetGatewayIdentifiersForEUIRequest)(nil), // 5: ttn.lorawan.v3.GetGatewayIdentifiersForEUIRequest - (*ListGatewaysRequest)(nil), // 6: ttn.lorawan.v3.ListGatewaysRequest - (*UpdateGatewayRequest)(nil), // 7: ttn.lorawan.v3.UpdateGatewayRequest - (*CreateGatewayAPIKeyRequest)(nil), // 8: ttn.lorawan.v3.CreateGatewayAPIKeyRequest - (*ListGatewayAPIKeysRequest)(nil), // 9: ttn.lorawan.v3.ListGatewayAPIKeysRequest - (*GetGatewayAPIKeyRequest)(nil), // 10: ttn.lorawan.v3.GetGatewayAPIKeyRequest - (*UpdateGatewayAPIKeyRequest)(nil), // 11: ttn.lorawan.v3.UpdateGatewayAPIKeyRequest - (*GetGatewayCollaboratorRequest)(nil), // 12: ttn.lorawan.v3.GetGatewayCollaboratorRequest - (*SetGatewayCollaboratorRequest)(nil), // 13: ttn.lorawan.v3.SetGatewayCollaboratorRequest - (*ListGatewayCollaboratorsRequest)(nil), // 14: ttn.lorawan.v3.ListGatewayCollaboratorsRequest - (*Gateway)(nil), // 15: ttn.lorawan.v3.Gateway - (*Gateways)(nil), // 16: ttn.lorawan.v3.Gateways - (*emptypb.Empty)(nil), // 17: google.protobuf.Empty - (*Rights)(nil), // 18: ttn.lorawan.v3.Rights - (*APIKey)(nil), // 19: ttn.lorawan.v3.APIKey - (*APIKeys)(nil), // 20: ttn.lorawan.v3.APIKeys - (*GetCollaboratorResponse)(nil), // 21: ttn.lorawan.v3.GetCollaboratorResponse - (*Collaborators)(nil), // 22: ttn.lorawan.v3.Collaborators + (*AssertGatewayRightsRequest)(nil), // 1: ttn.lorawan.v3.AssertGatewayRightsRequest + (*GatewayIdentifiers)(nil), // 2: ttn.lorawan.v3.GatewayIdentifiers + (*fieldmaskpb.FieldMask)(nil), // 3: google.protobuf.FieldMask + (*Rights)(nil), // 4: ttn.lorawan.v3.Rights + (*CreateGatewayRequest)(nil), // 5: ttn.lorawan.v3.CreateGatewayRequest + (*GetGatewayRequest)(nil), // 6: ttn.lorawan.v3.GetGatewayRequest + (*GetGatewayIdentifiersForEUIRequest)(nil), // 7: ttn.lorawan.v3.GetGatewayIdentifiersForEUIRequest + (*ListGatewaysRequest)(nil), // 8: ttn.lorawan.v3.ListGatewaysRequest + (*UpdateGatewayRequest)(nil), // 9: ttn.lorawan.v3.UpdateGatewayRequest + (*CreateGatewayAPIKeyRequest)(nil), // 10: ttn.lorawan.v3.CreateGatewayAPIKeyRequest + (*ListGatewayAPIKeysRequest)(nil), // 11: ttn.lorawan.v3.ListGatewayAPIKeysRequest + (*GetGatewayAPIKeyRequest)(nil), // 12: ttn.lorawan.v3.GetGatewayAPIKeyRequest + (*UpdateGatewayAPIKeyRequest)(nil), // 13: ttn.lorawan.v3.UpdateGatewayAPIKeyRequest + (*GetGatewayCollaboratorRequest)(nil), // 14: ttn.lorawan.v3.GetGatewayCollaboratorRequest + (*SetGatewayCollaboratorRequest)(nil), // 15: ttn.lorawan.v3.SetGatewayCollaboratorRequest + (*ListGatewayCollaboratorsRequest)(nil), // 16: ttn.lorawan.v3.ListGatewayCollaboratorsRequest + (*Gateway)(nil), // 17: ttn.lorawan.v3.Gateway + (*Gateways)(nil), // 18: ttn.lorawan.v3.Gateways + (*emptypb.Empty)(nil), // 19: google.protobuf.Empty + (*APIKey)(nil), // 20: ttn.lorawan.v3.APIKey + (*APIKeys)(nil), // 21: ttn.lorawan.v3.APIKeys + (*GetCollaboratorResponse)(nil), // 22: ttn.lorawan.v3.GetCollaboratorResponse + (*Collaborators)(nil), // 23: ttn.lorawan.v3.Collaborators } var file_ttn_lorawan_v3_gateway_services_proto_depIdxs = []int32{ - 1, // 0: ttn.lorawan.v3.PullGatewayConfigurationRequest.gateway_ids:type_name -> ttn.lorawan.v3.GatewayIdentifiers - 2, // 1: ttn.lorawan.v3.PullGatewayConfigurationRequest.field_mask:type_name -> google.protobuf.FieldMask - 3, // 2: ttn.lorawan.v3.GatewayRegistry.Create:input_type -> ttn.lorawan.v3.CreateGatewayRequest - 4, // 3: ttn.lorawan.v3.GatewayRegistry.Get:input_type -> ttn.lorawan.v3.GetGatewayRequest - 5, // 4: ttn.lorawan.v3.GatewayRegistry.GetIdentifiersForEUI:input_type -> ttn.lorawan.v3.GetGatewayIdentifiersForEUIRequest - 6, // 5: ttn.lorawan.v3.GatewayRegistry.List:input_type -> ttn.lorawan.v3.ListGatewaysRequest - 7, // 6: ttn.lorawan.v3.GatewayRegistry.Update:input_type -> ttn.lorawan.v3.UpdateGatewayRequest - 1, // 7: ttn.lorawan.v3.GatewayRegistry.Delete:input_type -> ttn.lorawan.v3.GatewayIdentifiers - 1, // 8: ttn.lorawan.v3.GatewayRegistry.Restore:input_type -> ttn.lorawan.v3.GatewayIdentifiers - 1, // 9: ttn.lorawan.v3.GatewayRegistry.Purge:input_type -> ttn.lorawan.v3.GatewayIdentifiers - 1, // 10: ttn.lorawan.v3.GatewayAccess.ListRights:input_type -> ttn.lorawan.v3.GatewayIdentifiers - 8, // 11: ttn.lorawan.v3.GatewayAccess.CreateAPIKey:input_type -> ttn.lorawan.v3.CreateGatewayAPIKeyRequest - 9, // 12: ttn.lorawan.v3.GatewayAccess.ListAPIKeys:input_type -> ttn.lorawan.v3.ListGatewayAPIKeysRequest - 10, // 13: ttn.lorawan.v3.GatewayAccess.GetAPIKey:input_type -> ttn.lorawan.v3.GetGatewayAPIKeyRequest - 11, // 14: ttn.lorawan.v3.GatewayAccess.UpdateAPIKey:input_type -> ttn.lorawan.v3.UpdateGatewayAPIKeyRequest - 12, // 15: ttn.lorawan.v3.GatewayAccess.GetCollaborator:input_type -> ttn.lorawan.v3.GetGatewayCollaboratorRequest - 13, // 16: ttn.lorawan.v3.GatewayAccess.SetCollaborator:input_type -> ttn.lorawan.v3.SetGatewayCollaboratorRequest - 14, // 17: ttn.lorawan.v3.GatewayAccess.ListCollaborators:input_type -> ttn.lorawan.v3.ListGatewayCollaboratorsRequest - 0, // 18: ttn.lorawan.v3.GatewayConfigurator.PullConfiguration:input_type -> ttn.lorawan.v3.PullGatewayConfigurationRequest - 15, // 19: ttn.lorawan.v3.GatewayRegistry.Create:output_type -> ttn.lorawan.v3.Gateway - 15, // 20: ttn.lorawan.v3.GatewayRegistry.Get:output_type -> ttn.lorawan.v3.Gateway - 1, // 21: ttn.lorawan.v3.GatewayRegistry.GetIdentifiersForEUI:output_type -> ttn.lorawan.v3.GatewayIdentifiers - 16, // 22: ttn.lorawan.v3.GatewayRegistry.List:output_type -> ttn.lorawan.v3.Gateways - 15, // 23: ttn.lorawan.v3.GatewayRegistry.Update:output_type -> ttn.lorawan.v3.Gateway - 17, // 24: ttn.lorawan.v3.GatewayRegistry.Delete:output_type -> google.protobuf.Empty - 17, // 25: ttn.lorawan.v3.GatewayRegistry.Restore:output_type -> google.protobuf.Empty - 17, // 26: ttn.lorawan.v3.GatewayRegistry.Purge:output_type -> google.protobuf.Empty - 18, // 27: ttn.lorawan.v3.GatewayAccess.ListRights:output_type -> ttn.lorawan.v3.Rights - 19, // 28: ttn.lorawan.v3.GatewayAccess.CreateAPIKey:output_type -> ttn.lorawan.v3.APIKey - 20, // 29: ttn.lorawan.v3.GatewayAccess.ListAPIKeys:output_type -> ttn.lorawan.v3.APIKeys - 19, // 30: ttn.lorawan.v3.GatewayAccess.GetAPIKey:output_type -> ttn.lorawan.v3.APIKey - 19, // 31: ttn.lorawan.v3.GatewayAccess.UpdateAPIKey:output_type -> ttn.lorawan.v3.APIKey - 21, // 32: ttn.lorawan.v3.GatewayAccess.GetCollaborator:output_type -> ttn.lorawan.v3.GetCollaboratorResponse - 17, // 33: ttn.lorawan.v3.GatewayAccess.SetCollaborator:output_type -> google.protobuf.Empty - 22, // 34: ttn.lorawan.v3.GatewayAccess.ListCollaborators:output_type -> ttn.lorawan.v3.Collaborators - 15, // 35: ttn.lorawan.v3.GatewayConfigurator.PullConfiguration:output_type -> ttn.lorawan.v3.Gateway - 19, // [19:36] is the sub-list for method output_type - 2, // [2:19] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 2, // 0: ttn.lorawan.v3.PullGatewayConfigurationRequest.gateway_ids:type_name -> ttn.lorawan.v3.GatewayIdentifiers + 3, // 1: ttn.lorawan.v3.PullGatewayConfigurationRequest.field_mask:type_name -> google.protobuf.FieldMask + 2, // 2: ttn.lorawan.v3.AssertGatewayRightsRequest.gateway_ids:type_name -> ttn.lorawan.v3.GatewayIdentifiers + 4, // 3: ttn.lorawan.v3.AssertGatewayRightsRequest.required:type_name -> ttn.lorawan.v3.Rights + 5, // 4: ttn.lorawan.v3.GatewayRegistry.Create:input_type -> ttn.lorawan.v3.CreateGatewayRequest + 6, // 5: ttn.lorawan.v3.GatewayRegistry.Get:input_type -> ttn.lorawan.v3.GetGatewayRequest + 7, // 6: ttn.lorawan.v3.GatewayRegistry.GetIdentifiersForEUI:input_type -> ttn.lorawan.v3.GetGatewayIdentifiersForEUIRequest + 8, // 7: ttn.lorawan.v3.GatewayRegistry.List:input_type -> ttn.lorawan.v3.ListGatewaysRequest + 9, // 8: ttn.lorawan.v3.GatewayRegistry.Update:input_type -> ttn.lorawan.v3.UpdateGatewayRequest + 2, // 9: ttn.lorawan.v3.GatewayRegistry.Delete:input_type -> ttn.lorawan.v3.GatewayIdentifiers + 2, // 10: ttn.lorawan.v3.GatewayRegistry.Restore:input_type -> ttn.lorawan.v3.GatewayIdentifiers + 2, // 11: ttn.lorawan.v3.GatewayRegistry.Purge:input_type -> ttn.lorawan.v3.GatewayIdentifiers + 2, // 12: ttn.lorawan.v3.GatewayAccess.ListRights:input_type -> ttn.lorawan.v3.GatewayIdentifiers + 10, // 13: ttn.lorawan.v3.GatewayAccess.CreateAPIKey:input_type -> ttn.lorawan.v3.CreateGatewayAPIKeyRequest + 11, // 14: ttn.lorawan.v3.GatewayAccess.ListAPIKeys:input_type -> ttn.lorawan.v3.ListGatewayAPIKeysRequest + 12, // 15: ttn.lorawan.v3.GatewayAccess.GetAPIKey:input_type -> ttn.lorawan.v3.GetGatewayAPIKeyRequest + 13, // 16: ttn.lorawan.v3.GatewayAccess.UpdateAPIKey:input_type -> ttn.lorawan.v3.UpdateGatewayAPIKeyRequest + 14, // 17: ttn.lorawan.v3.GatewayAccess.GetCollaborator:input_type -> ttn.lorawan.v3.GetGatewayCollaboratorRequest + 15, // 18: ttn.lorawan.v3.GatewayAccess.SetCollaborator:input_type -> ttn.lorawan.v3.SetGatewayCollaboratorRequest + 16, // 19: ttn.lorawan.v3.GatewayAccess.ListCollaborators:input_type -> ttn.lorawan.v3.ListGatewayCollaboratorsRequest + 0, // 20: ttn.lorawan.v3.GatewayConfigurator.PullConfiguration:input_type -> ttn.lorawan.v3.PullGatewayConfigurationRequest + 1, // 21: ttn.lorawan.v3.GatewayBatchAccess.AssertRights:input_type -> ttn.lorawan.v3.AssertGatewayRightsRequest + 17, // 22: ttn.lorawan.v3.GatewayRegistry.Create:output_type -> ttn.lorawan.v3.Gateway + 17, // 23: ttn.lorawan.v3.GatewayRegistry.Get:output_type -> ttn.lorawan.v3.Gateway + 2, // 24: ttn.lorawan.v3.GatewayRegistry.GetIdentifiersForEUI:output_type -> ttn.lorawan.v3.GatewayIdentifiers + 18, // 25: ttn.lorawan.v3.GatewayRegistry.List:output_type -> ttn.lorawan.v3.Gateways + 17, // 26: ttn.lorawan.v3.GatewayRegistry.Update:output_type -> ttn.lorawan.v3.Gateway + 19, // 27: ttn.lorawan.v3.GatewayRegistry.Delete:output_type -> google.protobuf.Empty + 19, // 28: ttn.lorawan.v3.GatewayRegistry.Restore:output_type -> google.protobuf.Empty + 19, // 29: ttn.lorawan.v3.GatewayRegistry.Purge:output_type -> google.protobuf.Empty + 4, // 30: ttn.lorawan.v3.GatewayAccess.ListRights:output_type -> ttn.lorawan.v3.Rights + 20, // 31: ttn.lorawan.v3.GatewayAccess.CreateAPIKey:output_type -> ttn.lorawan.v3.APIKey + 21, // 32: ttn.lorawan.v3.GatewayAccess.ListAPIKeys:output_type -> ttn.lorawan.v3.APIKeys + 20, // 33: ttn.lorawan.v3.GatewayAccess.GetAPIKey:output_type -> ttn.lorawan.v3.APIKey + 20, // 34: ttn.lorawan.v3.GatewayAccess.UpdateAPIKey:output_type -> ttn.lorawan.v3.APIKey + 22, // 35: ttn.lorawan.v3.GatewayAccess.GetCollaborator:output_type -> ttn.lorawan.v3.GetCollaboratorResponse + 19, // 36: ttn.lorawan.v3.GatewayAccess.SetCollaborator:output_type -> google.protobuf.Empty + 23, // 37: ttn.lorawan.v3.GatewayAccess.ListCollaborators:output_type -> ttn.lorawan.v3.Collaborators + 17, // 38: ttn.lorawan.v3.GatewayConfigurator.PullConfiguration:output_type -> ttn.lorawan.v3.Gateway + 19, // 39: ttn.lorawan.v3.GatewayBatchAccess.AssertRights:output_type -> google.protobuf.Empty + 22, // [22:40] is the sub-list for method output_type + 4, // [4:22] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_ttn_lorawan_v3_gateway_services_proto_init() } @@ -390,6 +472,18 @@ func file_ttn_lorawan_v3_gateway_services_proto_init() { return nil } } + file_ttn_lorawan_v3_gateway_services_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AssertGatewayRightsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -397,9 +491,9 @@ func file_ttn_lorawan_v3_gateway_services_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_ttn_lorawan_v3_gateway_services_proto_rawDesc, NumEnums: 0, - NumMessages: 1, + NumMessages: 2, NumExtensions: 0, - NumServices: 3, + NumServices: 4, }, GoTypes: file_ttn_lorawan_v3_gateway_services_proto_goTypes, DependencyIndexes: file_ttn_lorawan_v3_gateway_services_proto_depIdxs, diff --git a/pkg/ttnpb/gateway_services.pb.gw.go b/pkg/ttnpb/gateway_services.pb.gw.go index dca46f5b19..c0c5e511dd 100644 --- a/pkg/ttnpb/gateway_services.pb.gw.go +++ b/pkg/ttnpb/gateway_services.pb.gw.go @@ -1395,6 +1395,42 @@ func local_request_GatewayAccess_ListCollaborators_0(ctx context.Context, marsha } +var ( + filter_GatewayBatchAccess_AssertRights_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GatewayBatchAccess_AssertRights_0(ctx context.Context, marshaler runtime.Marshaler, client GatewayBatchAccessClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AssertGatewayRightsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GatewayBatchAccess_AssertRights_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.AssertRights(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GatewayBatchAccess_AssertRights_0(ctx context.Context, marshaler runtime.Marshaler, server GatewayBatchAccessServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AssertGatewayRightsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GatewayBatchAccess_AssertRights_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.AssertRights(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterGatewayRegistryHandlerServer registers the http handlers for service GatewayRegistry to "mux". // UnaryRPC :call GatewayRegistryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -1888,6 +1924,40 @@ func RegisterGatewayAccessHandlerServer(ctx context.Context, mux *runtime.ServeM return nil } +// RegisterGatewayBatchAccessHandlerServer registers the http handlers for service GatewayBatchAccess to "mux". +// UnaryRPC :call GatewayBatchAccessServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterGatewayBatchAccessHandlerFromEndpoint instead. +func RegisterGatewayBatchAccessHandlerServer(ctx context.Context, mux *runtime.ServeMux, server GatewayBatchAccessServer) error { + + mux.Handle("GET", pattern_GatewayBatchAccess_AssertRights_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayBatchAccess/AssertRights", runtime.WithHTTPPathPattern("/gateways/rights/batch")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GatewayBatchAccess_AssertRights_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_GatewayBatchAccess_AssertRights_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + // RegisterGatewayRegistryHandlerFromEndpoint is same as RegisterGatewayRegistryHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterGatewayRegistryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { @@ -2471,3 +2541,74 @@ var ( forward_GatewayAccess_ListCollaborators_0 = runtime.ForwardResponseMessage ) + +// RegisterGatewayBatchAccessHandlerFromEndpoint is same as RegisterGatewayBatchAccessHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterGatewayBatchAccessHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.DialContext(ctx, endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterGatewayBatchAccessHandler(ctx, mux, conn) +} + +// RegisterGatewayBatchAccessHandler registers the http handlers for service GatewayBatchAccess to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterGatewayBatchAccessHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterGatewayBatchAccessHandlerClient(ctx, mux, NewGatewayBatchAccessClient(conn)) +} + +// RegisterGatewayBatchAccessHandlerClient registers the http handlers for service GatewayBatchAccess +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "GatewayBatchAccessClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "GatewayBatchAccessClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "GatewayBatchAccessClient" to call the correct interceptors. +func RegisterGatewayBatchAccessHandlerClient(ctx context.Context, mux *runtime.ServeMux, client GatewayBatchAccessClient) error { + + mux.Handle("GET", pattern_GatewayBatchAccess_AssertRights_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayBatchAccess/AssertRights", runtime.WithHTTPPathPattern("/gateways/rights/batch")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GatewayBatchAccess_AssertRights_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_GatewayBatchAccess_AssertRights_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_GatewayBatchAccess_AssertRights_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"gateways", "rights", "batch"}, "")) +) + +var ( + forward_GatewayBatchAccess_AssertRights_0 = runtime.ForwardResponseMessage +) diff --git a/pkg/ttnpb/gateway_services.pb.paths.fm.go b/pkg/ttnpb/gateway_services.pb.paths.fm.go index 78f683e9f6..3a994f2120 100644 --- a/pkg/ttnpb/gateway_services.pb.paths.fm.go +++ b/pkg/ttnpb/gateway_services.pb.paths.fm.go @@ -13,3 +13,13 @@ var PullGatewayConfigurationRequestFieldPathsTopLevel = []string{ "field_mask", "gateway_ids", } +var AssertGatewayRightsRequestFieldPathsNested = []string{ + "gateway_ids", + "required", + "required.rights", +} + +var AssertGatewayRightsRequestFieldPathsTopLevel = []string{ + "gateway_ids", + "required", +} diff --git a/pkg/ttnpb/gateway_services.pb.setters.fm.go b/pkg/ttnpb/gateway_services.pb.setters.fm.go index 1b363dca7e..c8e7bc2182 100644 --- a/pkg/ttnpb/gateway_services.pb.setters.fm.go +++ b/pkg/ttnpb/gateway_services.pb.setters.fm.go @@ -48,3 +48,48 @@ func (dst *PullGatewayConfigurationRequest) SetFields(src *PullGatewayConfigurat } return nil } + +func (dst *AssertGatewayRightsRequest) SetFields(src *AssertGatewayRightsRequest, paths ...string) error { + for name, subs := range _processPaths(paths) { + switch name { + case "gateway_ids": + if len(subs) > 0 { + return fmt.Errorf("'gateway_ids' has no subfields, but %s were specified", subs) + } + if src != nil { + dst.GatewayIds = src.GatewayIds + } else { + dst.GatewayIds = nil + } + case "required": + if len(subs) > 0 { + var newDst, newSrc *Rights + if (src == nil || src.Required == nil) && dst.Required == nil { + continue + } + if src != nil { + newSrc = src.Required + } + if dst.Required != nil { + newDst = dst.Required + } else { + newDst = &Rights{} + dst.Required = newDst + } + if err := newDst.SetFields(newSrc, subs...); err != nil { + return err + } + } else { + if src != nil { + dst.Required = src.Required + } else { + dst.Required = nil + } + } + + default: + return fmt.Errorf("invalid field: '%s'", name) + } + } + return nil +} diff --git a/pkg/ttnpb/gateway_services.pb.validate.go b/pkg/ttnpb/gateway_services.pb.validate.go index aa68b62362..8061e73113 100644 --- a/pkg/ttnpb/gateway_services.pb.validate.go +++ b/pkg/ttnpb/gateway_services.pb.validate.go @@ -137,3 +137,128 @@ var _ interface { Cause() error ErrorName() string } = PullGatewayConfigurationRequestValidationError{} + +// ValidateFields checks the field values on AssertGatewayRightsRequest with +// the rules defined in the proto definition for this message. If any rules +// are violated, an error is returned. +func (m *AssertGatewayRightsRequest) ValidateFields(paths ...string) error { + if m == nil { + return nil + } + + if len(paths) == 0 { + paths = AssertGatewayRightsRequestFieldPathsNested + } + + for name, subs := range _processPaths(append(paths[:0:0], paths...)) { + _ = subs + switch name { + case "gateway_ids": + + if l := len(m.GetGatewayIds()); l < 1 || l > 20 { + return AssertGatewayRightsRequestValidationError{ + field: "gateway_ids", + reason: "value must contain between 1 and 20 items, inclusive", + } + } + + for idx, item := range m.GetGatewayIds() { + _, _ = idx, item + + if v, ok := interface{}(item).(interface{ ValidateFields(...string) error }); ok { + if err := v.ValidateFields(subs...); err != nil { + return AssertGatewayRightsRequestValidationError{ + field: fmt.Sprintf("gateway_ids[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + case "required": + + if m.GetRequired() == nil { + return AssertGatewayRightsRequestValidationError{ + field: "required", + reason: "value is required", + } + } + + if v, ok := interface{}(m.GetRequired()).(interface{ ValidateFields(...string) error }); ok { + if err := v.ValidateFields(subs...); err != nil { + return AssertGatewayRightsRequestValidationError{ + field: "required", + reason: "embedded message failed validation", + cause: err, + } + } + } + + default: + return AssertGatewayRightsRequestValidationError{ + field: name, + reason: "invalid field path", + } + } + } + return nil +} + +// AssertGatewayRightsRequestValidationError is the validation error returned +// by AssertGatewayRightsRequest.ValidateFields if the designated constraints +// aren't met. +type AssertGatewayRightsRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e AssertGatewayRightsRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e AssertGatewayRightsRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e AssertGatewayRightsRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e AssertGatewayRightsRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e AssertGatewayRightsRequestValidationError) ErrorName() string { + return "AssertGatewayRightsRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e AssertGatewayRightsRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sAssertGatewayRightsRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = AssertGatewayRightsRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = AssertGatewayRightsRequestValidationError{} diff --git a/pkg/ttnpb/gateway_services_grpc.pb.go b/pkg/ttnpb/gateway_services_grpc.pb.go index f7fa8f5108..2b7fc19a4e 100644 --- a/pkg/ttnpb/gateway_services_grpc.pb.go +++ b/pkg/ttnpb/gateway_services_grpc.pb.go @@ -915,3 +915,97 @@ var GatewayConfigurator_ServiceDesc = grpc.ServiceDesc{ }, Metadata: "ttn/lorawan/v3/gateway_services.proto", } + +const ( + GatewayBatchAccess_AssertRights_FullMethodName = "/ttn.lorawan.v3.GatewayBatchAccess/AssertRights" +) + +// GatewayBatchAccessClient is the client API for GatewayBatchAccess service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GatewayBatchAccessClient interface { + // Assert that the caller has the requested rights on all the requested gateways. + // The check is successful if there are no errors. + AssertRights(ctx context.Context, in *AssertGatewayRightsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type gatewayBatchAccessClient struct { + cc grpc.ClientConnInterface +} + +func NewGatewayBatchAccessClient(cc grpc.ClientConnInterface) GatewayBatchAccessClient { + return &gatewayBatchAccessClient{cc} +} + +func (c *gatewayBatchAccessClient) AssertRights(ctx context.Context, in *AssertGatewayRightsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, GatewayBatchAccess_AssertRights_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GatewayBatchAccessServer is the server API for GatewayBatchAccess service. +// All implementations must embed UnimplementedGatewayBatchAccessServer +// for forward compatibility +type GatewayBatchAccessServer interface { + // Assert that the caller has the requested rights on all the requested gateways. + // The check is successful if there are no errors. + AssertRights(context.Context, *AssertGatewayRightsRequest) (*emptypb.Empty, error) + mustEmbedUnimplementedGatewayBatchAccessServer() +} + +// UnimplementedGatewayBatchAccessServer must be embedded to have forward compatible implementations. +type UnimplementedGatewayBatchAccessServer struct { +} + +func (UnimplementedGatewayBatchAccessServer) AssertRights(context.Context, *AssertGatewayRightsRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method AssertRights not implemented") +} +func (UnimplementedGatewayBatchAccessServer) mustEmbedUnimplementedGatewayBatchAccessServer() {} + +// UnsafeGatewayBatchAccessServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GatewayBatchAccessServer will +// result in compilation errors. +type UnsafeGatewayBatchAccessServer interface { + mustEmbedUnimplementedGatewayBatchAccessServer() +} + +func RegisterGatewayBatchAccessServer(s grpc.ServiceRegistrar, srv GatewayBatchAccessServer) { + s.RegisterService(&GatewayBatchAccess_ServiceDesc, srv) +} + +func _GatewayBatchAccess_AssertRights_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AssertGatewayRightsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GatewayBatchAccessServer).AssertRights(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GatewayBatchAccess_AssertRights_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GatewayBatchAccessServer).AssertRights(ctx, req.(*AssertGatewayRightsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// GatewayBatchAccess_ServiceDesc is the grpc.ServiceDesc for GatewayBatchAccess service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var GatewayBatchAccess_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "ttn.lorawan.v3.GatewayBatchAccess", + HandlerType: (*GatewayBatchAccessServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AssertRights", + Handler: _GatewayBatchAccess_AssertRights_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ttn/lorawan/v3/gateway_services.proto", +} diff --git a/pkg/ttnpb/gateway_services_json.pb.go b/pkg/ttnpb/gateway_services_json.pb.go index abb7b0ee2e..548e8fbc72 100644 --- a/pkg/ttnpb/gateway_services_json.pb.go +++ b/pkg/ttnpb/gateway_services_json.pb.go @@ -76,3 +76,78 @@ func (x *PullGatewayConfigurationRequest) UnmarshalProtoJSON(s *jsonplugin.Unmar func (x *PullGatewayConfigurationRequest) UnmarshalJSON(b []byte) error { return jsonplugin.DefaultUnmarshalerConfig.Unmarshal(b, x) } + +// MarshalProtoJSON marshals the AssertGatewayRightsRequest message to JSON. +func (x *AssertGatewayRightsRequest) MarshalProtoJSON(s *jsonplugin.MarshalState) { + if x == nil { + s.WriteNil() + return + } + s.WriteObjectStart() + var wroteField bool + if len(x.GatewayIds) > 0 || s.HasField("gateway_ids") { + s.WriteMoreIf(&wroteField) + s.WriteObjectField("gateway_ids") + s.WriteArrayStart() + var wroteElement bool + for _, element := range x.GatewayIds { + s.WriteMoreIf(&wroteElement) + element.MarshalProtoJSON(s.WithField("gateway_ids")) + } + s.WriteArrayEnd() + } + if x.Required != nil || s.HasField("required") { + s.WriteMoreIf(&wroteField) + s.WriteObjectField("required") + x.Required.MarshalProtoJSON(s.WithField("required")) + } + s.WriteObjectEnd() +} + +// MarshalJSON marshals the AssertGatewayRightsRequest to JSON. +func (x *AssertGatewayRightsRequest) MarshalJSON() ([]byte, error) { + return jsonplugin.DefaultMarshalerConfig.Marshal(x) +} + +// UnmarshalProtoJSON unmarshals the AssertGatewayRightsRequest message from JSON. +func (x *AssertGatewayRightsRequest) UnmarshalProtoJSON(s *jsonplugin.UnmarshalState) { + if s.ReadNil() { + return + } + s.ReadObject(func(key string) { + switch key { + default: + s.ReadAny() // ignore unknown field + case "gateway_ids", "gatewayIds": + s.AddField("gateway_ids") + if s.ReadNil() { + x.GatewayIds = nil + return + } + s.ReadArray(func() { + if s.ReadNil() { + x.GatewayIds = append(x.GatewayIds, nil) + return + } + v := &GatewayIdentifiers{} + v.UnmarshalProtoJSON(s.WithField("gateway_ids", false)) + if s.Err() != nil { + return + } + x.GatewayIds = append(x.GatewayIds, v) + }) + case "required": + if s.ReadNil() { + x.Required = nil + return + } + x.Required = &Rights{} + x.Required.UnmarshalProtoJSON(s.WithField("required", true)) + } + }) +} + +// UnmarshalJSON unmarshals the AssertGatewayRightsRequest from JSON. +func (x *AssertGatewayRightsRequest) UnmarshalJSON(b []byte) error { + return jsonplugin.DefaultUnmarshalerConfig.Unmarshal(b, x) +} diff --git a/pkg/ttnpb/gatewayserver_grpc.pb.go b/pkg/ttnpb/gatewayserver_grpc.pb.go index 13b7b964f9..acd331bec1 100644 --- a/pkg/ttnpb/gatewayserver_grpc.pb.go +++ b/pkg/ttnpb/gatewayserver_grpc.pb.go @@ -384,9 +384,10 @@ type GsClient interface { // This is not persisted between reconnects. GetGatewayConnectionStats(ctx context.Context, in *GatewayIdentifiers, opts ...grpc.CallOption) (*GatewayConnectionStats, error) // Get statistics about gateway connections to the Gateway Server of a batch of gateways. - // This is not persisted between reconnects. - // Gateways that are not connected or are part of a different cluster are ignored. - // It is up to the client to make sure that the gateways are in the requested cluster. + // - Statistics are not persisted between reconnects. + // - Gateways that are not connected or are part of a different cluster are ignored. + // - The client should ensure that the requested gateways are in the requested cluster. + // - The client should have the right to get the gateway connection stats on all requested gateways. BatchGetGatewayConnectionStats(ctx context.Context, in *BatchGetGatewayConnectionStatsRequest, opts ...grpc.CallOption) (*BatchGetGatewayConnectionStatsResponse, error) } @@ -424,9 +425,10 @@ type GsServer interface { // This is not persisted between reconnects. GetGatewayConnectionStats(context.Context, *GatewayIdentifiers) (*GatewayConnectionStats, error) // Get statistics about gateway connections to the Gateway Server of a batch of gateways. - // This is not persisted between reconnects. - // Gateways that are not connected or are part of a different cluster are ignored. - // It is up to the client to make sure that the gateways are in the requested cluster. + // - Statistics are not persisted between reconnects. + // - Gateways that are not connected or are part of a different cluster are ignored. + // - The client should ensure that the requested gateways are in the requested cluster. + // - The client should have the right to get the gateway connection stats on all requested gateways. BatchGetGatewayConnectionStats(context.Context, *BatchGetGatewayConnectionStatsRequest) (*BatchGetGatewayConnectionStatsResponse, error) mustEmbedUnimplementedGsServer() } diff --git a/sdk/js/generated/api-definition.json b/sdk/js/generated/api-definition.json index 744df98877..80e20387b8 100644 --- a/sdk/js/generated/api-definition.json +++ b/sdk/js/generated/api-definition.json @@ -3187,6 +3187,18 @@ ] } }, + "GatewayBatchAccess": { + "AssertRights": { + "file": "ttn/lorawan/v3/gateway_services.proto", + "http": [ + { + "method": "get", + "pattern": "/gateways/rights/batch", + "parameters": [] + } + ] + } + }, "GatewayConfigurator": { "PullConfiguration": { "file": "ttn/lorawan/v3/gateway_services.proto", diff --git a/sdk/js/generated/api.json b/sdk/js/generated/api.json index 8157abc2aa..ec0b498db1 100644 --- a/sdk/js/generated/api.json +++ b/sdk/js/generated/api.json @@ -22545,6 +22545,62 @@ "enums": [], "extensions": [], "messages": [ + { + "name": "AssertGatewayRightsRequest", + "longName": "AssertGatewayRightsRequest", + "fullName": "ttn.lorawan.v3.AssertGatewayRightsRequest", + "description": "", + "hasExtensions": false, + "hasFields": true, + "hasOneofs": false, + "extensions": [], + "fields": [ + { + "name": "gateway_ids", + "description": "", + "label": "repeated", + "type": "GatewayIdentifiers", + "longType": "GatewayIdentifiers", + "fullType": "ttn.lorawan.v3.GatewayIdentifiers", + "ismap": false, + "isoneof": false, + "oneofdecl": "", + "defaultValue": "", + "options": { + "validate.rules": [ + { + "name": "repeated.min_items", + "value": 1 + }, + { + "name": "repeated.max_items", + "value": 20 + } + ] + } + }, + { + "name": "required", + "description": "", + "label": "", + "type": "Rights", + "longType": "Rights", + "fullType": "ttn.lorawan.v3.Rights", + "ismap": false, + "isoneof": false, + "oneofdecl": "", + "defaultValue": "", + "options": { + "validate.rules": [ + { + "name": "message.required", + "value": true + } + ] + } + } + ] + }, { "name": "PullGatewayConfigurationRequest", "longName": "PullGatewayConfigurationRequest", @@ -22587,7 +22643,7 @@ "name": "GatewayAccess", "longName": "GatewayAccess", "fullName": "ttn.lorawan.v3.GatewayAccess", - "description": "The GatewayAcces service, exposed by the Identity Server, is used to manage\nAPI keys and collaborators of gateways.", + "description": "The GatewayAccess service, exposed by the Identity Server, is used to manage\nAPI keys and collaborators of gateways.", "methods": [ { "name": "ListRights", @@ -22778,6 +22834,36 @@ } ] }, + { + "name": "GatewayBatchAccess", + "longName": "GatewayBatchAccess", + "fullName": "ttn.lorawan.v3.GatewayBatchAccess", + "description": "The GatewayBatchAccess service, exposed by the Identity Server, is used to\ncheck the rights of the caller on multiple gateways at once.\nEXPERIMENTAL: This service is subject to change.", + "methods": [ + { + "name": "AssertRights", + "description": "Assert that the caller has the requested rights on all the requested gateways.\nThe check is successful if there are no errors.", + "requestType": "AssertGatewayRightsRequest", + "requestLongType": "AssertGatewayRightsRequest", + "requestFullType": "ttn.lorawan.v3.AssertGatewayRightsRequest", + "requestStreaming": false, + "responseType": "Empty", + "responseLongType": ".google.protobuf.Empty", + "responseFullType": "google.protobuf.Empty", + "responseStreaming": false, + "options": { + "google.api.http": { + "rules": [ + { + "method": "GET", + "pattern": "/gateways/rights/batch" + } + ] + } + } + } + ] + }, { "name": "GatewayConfigurator", "longName": "GatewayConfigurator", @@ -23304,7 +23390,7 @@ }, { "name": "BatchGetGatewayConnectionStats", - "description": "Get statistics about gateway connections to the Gateway Server of a batch of gateways.\nThis is not persisted between reconnects.\nGateways that are not connected or are part of a different cluster are ignored.\nIt is up to the client to make sure that the gateways are in the requested cluster.", + "description": "Get statistics about gateway connections to the Gateway Server of a batch of gateways.\n- Statistics are not persisted between reconnects.\n- Gateways that are not connected or are part of a different cluster are ignored.\n- The client should ensure that the requested gateways are in the requested cluster.\n- The client should have the right to get the gateway connection stats on all requested gateways.", "requestType": "BatchGetGatewayConnectionStatsRequest", "requestLongType": "BatchGetGatewayConnectionStatsRequest", "requestFullType": "ttn.lorawan.v3.BatchGetGatewayConnectionStatsRequest",