Sync SCIM related files to CE/main (#11037)

This commit is contained in:
Bianca 2025-12-02 15:44:43 +01:00 committed by GitHub
parent ff96dceedd
commit bfbd6a9a93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 1041 additions and 351 deletions

View file

@ -300,6 +300,18 @@ func (c *Logical) addExtraHeaders(r *Request, headers http.Header) error {
return nil
}
func (c *Logical) PatchRaw(path string, data []byte) (*Response, error) {
return c.PatchRawWithContext(context.Background(), path, data)
}
func (c *Logical) PatchRawWithContext(ctx context.Context, path string, data []byte) (*Response, error) {
r := c.c.NewRequest(http.MethodPatch, "/v1/"+path)
r.Headers.Set("Content-Type", "application/scim+json")
r.BodyBytes = data
return c.writeRaw(ctx, r)
}
// Recover recovers the data at the given Vault path from a loaded snapshot.
// The snapshotID parameter is the ID of the loaded snapshot
func (c *Logical) Recover(ctx context.Context, path string, snapshotID string) (*Secret, error) {

View file

@ -79,7 +79,13 @@ type Group struct {
// belongs to. Do not return this value over the API when reading the
// group.
// @inject_tag: sentinel:"-"
NamespaceID string `protobuf:"bytes,13,opt,name=namespace_id,json=namespaceID,proto3" json:"namespace_id,omitempty" sentinel:"-"`
NamespaceID string `protobuf:"bytes,13,opt,name=namespace_id,json=namespaceID,proto3" json:"namespace_id,omitempty" sentinel:"-"`
// ScimClientID records the unique identifier of the SCIM client that
// originally created this Group. This establishes a strict ownership model,
// ensuring that authoritative lifecycle operations (like updates and deletes by
// an IGA) can only be performed by the client that owns the resource.
// @inject_tag: sentinel:"-"
ScimClientID string `protobuf:"bytes,14,opt,name=scim_client_id,json=scimClientId,proto3" json:"scim_client_id,omitempty" sentinel:"-"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -205,6 +211,13 @@ func (x *Group) GetNamespaceID() string {
return ""
}
func (x *Group) GetScimClientID() string {
if x != nil {
return x.ScimClientID
}
return ""
}
// LocalAliases holds the aliases belonging to an entity that are local to the
// cluster.
type LocalAliases struct {
@ -312,7 +325,20 @@ type Entity struct {
// belongs to. Do not return this value over the API when reading the
// entity.
// @inject_tag: sentinel:"-"
NamespaceID string `protobuf:"bytes,12,opt,name=namespace_id,json=namespaceID,proto3" json:"namespace_id,omitempty" sentinel:"-"`
NamespaceID string `protobuf:"bytes,12,opt,name=namespace_id,json=namespaceID,proto3" json:"namespace_id,omitempty" sentinel:"-"`
// ExternalID stores the `externalId` provided by a SCIM client.
// This field serves as the canonical correlation key, linking this Vault Entity
// to its representation in the external identity management system. It is
// crucial for preventing the creation of duplicate entities when multiple
// SCIM clients provision the same user.
// @inject_tag: sentinel:"-"
ExternalID string `protobuf:"bytes,13,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty" sentinel:"-"`
// ScimClientID records the unique identifier of the SCIM client that
// originally created this Entity. This establishes a strict ownership model,
// ensuring that authoritative lifecycle operations (like updates and deletes by
// an IGA) can only be performed by the client that owns the resource.
// @inject_tag: sentinel:"-"
ScimClientID string `protobuf:"bytes,14,opt,name=scim_client_id,json=scimClientId,proto3" json:"scim_client_id,omitempty" sentinel:"-"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -431,6 +457,20 @@ func (x *Entity) GetNamespaceID() string {
return ""
}
func (x *Entity) GetExternalID() string {
if x != nil {
return x.ExternalID
}
return ""
}
func (x *Entity) GetScimClientID() string {
if x != nil {
return x.ScimClientID
}
return ""
}
// Alias represents the alias that gets stored inside of the
// entity object in storage and also represents in an in-memory index of an
// alias object.
@ -495,8 +535,14 @@ type Alias struct {
// during invalidation of local aliases in performance standbys.
// @inject_tag: sentinel:"-"
LocalBucketKey string `protobuf:"bytes,14,opt,name=local_bucket_key,json=localBucketKey,proto3" json:"local_bucket_key,omitempty" sentinel:"-"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
// ScimClientID records the unique identifier of the SCIM client that
// originally created this Alias. This establishes a strict ownership model,
// ensuring that authoritative lifecycle operations (like updates and deletes by
// an IGA) can only be performed by the client that owns the resource.
// @inject_tag: sentinel:"-"
ScimClientID string `protobuf:"bytes,15,opt,name=scim_client_id,json=scimClientId,proto3" json:"scim_client_id,omitempty" sentinel:"-"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Alias) Reset() {
@ -627,6 +673,94 @@ func (x *Alias) GetLocalBucketKey() string {
return ""
}
func (x *Alias) GetScimClientID() string {
if x != nil {
return x.ScimClientID
}
return ""
}
// ScimConfig defines the stored configuration for a single SCIM client.
// This configuration links a client's identity within Vault to its specific
// role and capabilities within the SCIM server.
type ScimConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
// ClientID is a unique, user-defined identifier for this specific SCIM
// client configuration (e.g., 'Okta-Prod', 'SailPoint-Dev').
ClientID string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
// ClientRole defines the client's function and authoritative power.
// It must be either "IGA" (authoritative) or "IDP" (standard).
ClientRole string `protobuf:"bytes,2,opt,name=client_role,json=clientRole,proto3" json:"client_role,omitempty"`
// AccessGrantPrincipal is the Vault Entity ID that represents the SCIM
// client application itself. This is the principal that will be granted the
// necessary permissions to perform SCIM operations.
AccessGrantPrincipal string `protobuf:"bytes,3,opt,name=access_grant_principal,json=accessGrantPrincipal,proto3" json:"access_grant_principal,omitempty"`
// AliasMountAccessor is an optional field that specifies the mount accessor
// of an auth method where login aliases should be created for provisioned users.
// This is typically used for clients with the 'IDP' role.
AliasMountAccessor string `protobuf:"bytes,4,opt,name=alias_mount_accessor,json=aliasMountAccessor,proto3" json:"alias_mount_accessor,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ScimConfig) Reset() {
*x = ScimConfig{}
mi := &file_helper_identity_types_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ScimConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ScimConfig) ProtoMessage() {}
func (x *ScimConfig) ProtoReflect() protoreflect.Message {
mi := &file_helper_identity_types_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ScimConfig.ProtoReflect.Descriptor instead.
func (*ScimConfig) Descriptor() ([]byte, []int) {
return file_helper_identity_types_proto_rawDescGZIP(), []int{4}
}
func (x *ScimConfig) GetClientID() string {
if x != nil {
return x.ClientID
}
return ""
}
func (x *ScimConfig) GetClientRole() string {
if x != nil {
return x.ClientRole
}
return ""
}
func (x *ScimConfig) GetAccessGrantPrincipal() string {
if x != nil {
return x.AccessGrantPrincipal
}
return ""
}
func (x *ScimConfig) GetAliasMountAccessor() string {
if x != nil {
return x.AliasMountAccessor
}
return ""
}
// Deprecated. Retained for backwards compatibility.
type EntityStorageEntry struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -646,7 +780,7 @@ type EntityStorageEntry struct {
func (x *EntityStorageEntry) Reset() {
*x = EntityStorageEntry{}
mi := &file_helper_identity_types_proto_msgTypes[4]
mi := &file_helper_identity_types_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -658,7 +792,7 @@ func (x *EntityStorageEntry) String() string {
func (*EntityStorageEntry) ProtoMessage() {}
func (x *EntityStorageEntry) ProtoReflect() protoreflect.Message {
mi := &file_helper_identity_types_proto_msgTypes[4]
mi := &file_helper_identity_types_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -671,7 +805,7 @@ func (x *EntityStorageEntry) ProtoReflect() protoreflect.Message {
// Deprecated: Use EntityStorageEntry.ProtoReflect.Descriptor instead.
func (*EntityStorageEntry) Descriptor() ([]byte, []int) {
return file_helper_identity_types_proto_rawDescGZIP(), []int{4}
return file_helper_identity_types_proto_rawDescGZIP(), []int{5}
}
func (x *EntityStorageEntry) GetPersonas() []*PersonaIndexEntry {
@ -763,7 +897,7 @@ type PersonaIndexEntry struct {
func (x *PersonaIndexEntry) Reset() {
*x = PersonaIndexEntry{}
mi := &file_helper_identity_types_proto_msgTypes[5]
mi := &file_helper_identity_types_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -775,7 +909,7 @@ func (x *PersonaIndexEntry) String() string {
func (*PersonaIndexEntry) ProtoMessage() {}
func (x *PersonaIndexEntry) ProtoReflect() protoreflect.Message {
mi := &file_helper_identity_types_proto_msgTypes[5]
mi := &file_helper_identity_types_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -788,7 +922,7 @@ func (x *PersonaIndexEntry) ProtoReflect() protoreflect.Message {
// Deprecated: Use PersonaIndexEntry.ProtoReflect.Descriptor instead.
func (*PersonaIndexEntry) Descriptor() ([]byte, []int) {
return file_helper_identity_types_proto_rawDescGZIP(), []int{5}
return file_helper_identity_types_proto_rawDescGZIP(), []int{6}
}
func (x *PersonaIndexEntry) GetID() string {
@ -870,7 +1004,7 @@ var file_helper_identity_types_proto_rawDesc = string([]byte{
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72,
0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x6d, 0x66, 0x61, 0x2f, 0x74, 0x79,
0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbc, 0x04, 0x0a, 0x05, 0x47, 0x72,
0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe2, 0x04, 0x0a, 0x05, 0x47, 0x72,
0x6f, 0x75, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63,
@ -902,178 +1036,198 @@ var file_helper_identity_types_proto_rawDesc = string([]byte{
0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0c, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65,
0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x39, 0x0a, 0x0c, 0x4c, 0x6f, 0x63, 0x61,
0x6c, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61,
0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x69, 0x64, 0x65, 0x6e,
0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61,
0x73, 0x65, 0x73, 0x22, 0x8c, 0x05, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x29,
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73,
0x63, 0x69, 0x6d, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x63, 0x69, 0x6d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
0x64, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x39,
0x0a, 0x0c, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x29,
0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x0f, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73,
0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a,
0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x1e, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72,
0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61,
0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65,
0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x65, 0x72,
0x67, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08,
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08,
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x63, 0x6b,
0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75,
0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x41, 0x0a, 0x0b, 0x6d, 0x66, 0x61, 0x5f, 0x73,
0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x69,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x4d,
0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a,
0x6d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61,
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4a, 0x0a, 0x0f, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63,
0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x66, 0x61,
0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0xe1, 0x05, 0x0a, 0x05, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x0e, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c,
0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0b, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x49, 0x64, 0x12,
0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25,
0x0a, 0x0e, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x63, 0x63,
0x65, 0x73, 0x73, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70,
0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x50, 0x61, 0x74, 0x68, 0x12, 0x39, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x22, 0xd3, 0x05, 0x0a, 0x06, 0x45, 0x6e,
0x74, 0x69, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65,
0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65,
0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f,
0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64,
0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x19, 0x6d, 0x65,
0x72, 0x67, 0x65, 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69,
0x63, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x6d,
0x65, 0x72, 0x67, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63,
0x61, 0x6c, 0x49, 0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x6d,
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x4c, 0x0a, 0x0f, 0x63, 0x75, 0x73, 0x74,
0x6f, 0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x23, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, 0x6c, 0x69,
0x61, 0x73, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18,
0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x28, 0x0a, 0x10,
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79,
0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x75, 0x63,
0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64,
0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28,
0x09, 0x52, 0x0f, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49,
0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x08,
0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x1d,
0x0a, 0x0a, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x41, 0x0a,
0x0b, 0x6d, 0x66, 0x61, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x20, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e,
0x74, 0x69, 0x74, 0x79, 0x2e, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73,
0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01,
0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c,
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12,
0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0d,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64,
0x12, 0x24, 0x0a, 0x0e, 0x73, 0x63, 0x69, 0x6d, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f,
0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x63, 0x69, 0x6d, 0x43, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
0x02, 0x38, 0x01, 0x1a, 0x41, 0x0a, 0x13, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74,
0x02, 0x38, 0x01, 0x1a, 0x4a, 0x0a, 0x0f, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74,
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x66, 0x61, 0x2e, 0x53, 0x65,
0x63, 0x72, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22,
0x87, 0x06, 0x0a, 0x05, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x61, 0x6e,
0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d,
0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73,
0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x61, 0x74,
0x68, 0x12, 0x39, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41,
0x6c, 0x69, 0x61, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d,
0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x19, 0x6d, 0x65, 0x72, 0x67, 0x65,
0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c,
0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x6d, 0x65, 0x72, 0x67,
0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x49,
0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x4c, 0x0a, 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f,
0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23,
0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x2e,
0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x52, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18, 0x0d, 0x20, 0x01,
0x28, 0x08, 0x52, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63,
0x61, 0x6c, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0e, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74,
0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x63, 0x69, 0x6d, 0x5f, 0x63, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x63, 0x69,
0x6d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x88, 0x05, 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x37, 0x0a,
0x08, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x1b, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f,
0x6e, 0x61, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x70, 0x65,
0x72, 0x73, 0x6f, 0x6e, 0x61, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x08, 0x6d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x69,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74,
0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54,
0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61,
0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x72,
0x67, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07,
0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x69,
0x74, 0x79, 0x49, 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65,
0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65,
0x73, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f,
0x68, 0x61, 0x73, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x62, 0x75, 0x63, 0x6b,
0x65, 0x74, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, 0x68, 0x12, 0x4d, 0x0a, 0x0b, 0x6d, 0x66, 0x61,
0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c,
0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79,
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x66, 0x61,
0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6d, 0x66,
0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61,
0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4a, 0x0a, 0x0f, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72,
0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x66, 0x61, 0x2e,
0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x01, 0x22, 0xf9, 0x03, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x49, 0x6e, 0x64,
0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69,
0x74, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79,
0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54,
0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63,
0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x6f, 0x75,
0x6e, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f,
0x75, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x45, 0x0a, 0x08, 0x6d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x69, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x49, 0x6e,
0x64, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70,
0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73,
0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x6d,
0x65, 0x72, 0x67, 0x65, 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x6d, 0x65, 0x72,
0x67, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x41, 0x0a, 0x13, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d,
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb2, 0x01, 0x0a, 0x0a, 0x53, 0x63,
0x69, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69,
0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f,
0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73,
0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72,
0x61, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x12, 0x30, 0x0a, 0x14,
0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65,
0x73, 0x73, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x61, 0x6c, 0x69, 0x61,
0x73, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x22, 0x88,
0x05, 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x37, 0x0a, 0x08, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x74, 0x79, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x73, 0x12, 0x0e,
0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x46, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e,
0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3f, 0x0a, 0x0d, 0x63, 0x72,
0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63,
0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6c,
0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d,
0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x74, 0x69,
0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x65,
0x72, 0x67, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x12, 0x1a, 0x0a,
0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52,
0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x75, 0x63,
0x6b, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x09, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0d, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73,
0x68, 0x12, 0x4d, 0x0a, 0x0b, 0x6d, 0x66, 0x61, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73,
0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73,
0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x2c, 0x5a,
0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68,
0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x68, 0x65, 0x6c, 0x70,
0x65, 0x72, 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4a, 0x0a,
0x0f, 0x4d, 0x66, 0x61, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x12, 0x21, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x66, 0x61, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf9, 0x03, 0x0a, 0x11, 0x50, 0x65,
0x72, 0x73, 0x6f, 0x6e, 0x61, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d,
0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73,
0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x61, 0x74,
0x68, 0x12, 0x45, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x50,
0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08,
0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x0d,
0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x44, 0x0a,
0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54,
0x69, 0x6d, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x5f, 0x66, 0x72,
0x6f, 0x6d, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20,
0x03, 0x28, 0x09, 0x52, 0x13, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x45,
0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61,
0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x76, 0x61,
0x75, 0x6c, 0x74, 0x2f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
@ -1088,50 +1242,51 @@ func file_helper_identity_types_proto_rawDescGZIP() []byte {
return file_helper_identity_types_proto_rawDescData
}
var file_helper_identity_types_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
var file_helper_identity_types_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
var file_helper_identity_types_proto_goTypes = []any{
(*Group)(nil), // 0: identity.Group
(*LocalAliases)(nil), // 1: identity.LocalAliases
(*Entity)(nil), // 2: identity.Entity
(*Alias)(nil), // 3: identity.Alias
(*EntityStorageEntry)(nil), // 4: identity.EntityStorageEntry
(*PersonaIndexEntry)(nil), // 5: identity.PersonaIndexEntry
nil, // 6: identity.Group.MetadataEntry
nil, // 7: identity.Entity.MetadataEntry
nil, // 8: identity.Entity.MFASecretsEntry
nil, // 9: identity.Alias.MetadataEntry
nil, // 10: identity.Alias.CustomMetadataEntry
nil, // 11: identity.EntityStorageEntry.MetadataEntry
nil, // 12: identity.EntityStorageEntry.MFASecretsEntry
nil, // 13: identity.PersonaIndexEntry.MetadataEntry
(*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp
(*mfa.Secret)(nil), // 15: mfa.Secret
(*ScimConfig)(nil), // 4: identity.ScimConfig
(*EntityStorageEntry)(nil), // 5: identity.EntityStorageEntry
(*PersonaIndexEntry)(nil), // 6: identity.PersonaIndexEntry
nil, // 7: identity.Group.MetadataEntry
nil, // 8: identity.Entity.MetadataEntry
nil, // 9: identity.Entity.MFASecretsEntry
nil, // 10: identity.Alias.MetadataEntry
nil, // 11: identity.Alias.CustomMetadataEntry
nil, // 12: identity.EntityStorageEntry.MetadataEntry
nil, // 13: identity.EntityStorageEntry.MFASecretsEntry
nil, // 14: identity.PersonaIndexEntry.MetadataEntry
(*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp
(*mfa.Secret)(nil), // 16: mfa.Secret
}
var file_helper_identity_types_proto_depIDxs = []int32{
6, // 0: identity.Group.metadata:type_name -> identity.Group.MetadataEntry
14, // 1: identity.Group.creation_time:type_name -> google.protobuf.Timestamp
14, // 2: identity.Group.last_update_time:type_name -> google.protobuf.Timestamp
7, // 0: identity.Group.metadata:type_name -> identity.Group.MetadataEntry
15, // 1: identity.Group.creation_time:type_name -> google.protobuf.Timestamp
15, // 2: identity.Group.last_update_time:type_name -> google.protobuf.Timestamp
3, // 3: identity.Group.alias:type_name -> identity.Alias
3, // 4: identity.LocalAliases.aliases:type_name -> identity.Alias
3, // 5: identity.Entity.aliases:type_name -> identity.Alias
7, // 6: identity.Entity.metadata:type_name -> identity.Entity.MetadataEntry
14, // 7: identity.Entity.creation_time:type_name -> google.protobuf.Timestamp
14, // 8: identity.Entity.last_update_time:type_name -> google.protobuf.Timestamp
8, // 9: identity.Entity.mfa_secrets:type_name -> identity.Entity.MFASecretsEntry
9, // 10: identity.Alias.metadata:type_name -> identity.Alias.MetadataEntry
14, // 11: identity.Alias.creation_time:type_name -> google.protobuf.Timestamp
14, // 12: identity.Alias.last_update_time:type_name -> google.protobuf.Timestamp
10, // 13: identity.Alias.custom_metadata:type_name -> identity.Alias.CustomMetadataEntry
5, // 14: identity.EntityStorageEntry.personas:type_name -> identity.PersonaIndexEntry
11, // 15: identity.EntityStorageEntry.metadata:type_name -> identity.EntityStorageEntry.MetadataEntry
14, // 16: identity.EntityStorageEntry.creation_time:type_name -> google.protobuf.Timestamp
14, // 17: identity.EntityStorageEntry.last_update_time:type_name -> google.protobuf.Timestamp
12, // 18: identity.EntityStorageEntry.mfa_secrets:type_name -> identity.EntityStorageEntry.MFASecretsEntry
13, // 19: identity.PersonaIndexEntry.metadata:type_name -> identity.PersonaIndexEntry.MetadataEntry
14, // 20: identity.PersonaIndexEntry.creation_time:type_name -> google.protobuf.Timestamp
14, // 21: identity.PersonaIndexEntry.last_update_time:type_name -> google.protobuf.Timestamp
15, // 22: identity.Entity.MFASecretsEntry.value:type_name -> mfa.Secret
15, // 23: identity.EntityStorageEntry.MFASecretsEntry.value:type_name -> mfa.Secret
8, // 6: identity.Entity.metadata:type_name -> identity.Entity.MetadataEntry
15, // 7: identity.Entity.creation_time:type_name -> google.protobuf.Timestamp
15, // 8: identity.Entity.last_update_time:type_name -> google.protobuf.Timestamp
9, // 9: identity.Entity.mfa_secrets:type_name -> identity.Entity.MFASecretsEntry
10, // 10: identity.Alias.metadata:type_name -> identity.Alias.MetadataEntry
15, // 11: identity.Alias.creation_time:type_name -> google.protobuf.Timestamp
15, // 12: identity.Alias.last_update_time:type_name -> google.protobuf.Timestamp
11, // 13: identity.Alias.custom_metadata:type_name -> identity.Alias.CustomMetadataEntry
6, // 14: identity.EntityStorageEntry.personas:type_name -> identity.PersonaIndexEntry
12, // 15: identity.EntityStorageEntry.metadata:type_name -> identity.EntityStorageEntry.MetadataEntry
15, // 16: identity.EntityStorageEntry.creation_time:type_name -> google.protobuf.Timestamp
15, // 17: identity.EntityStorageEntry.last_update_time:type_name -> google.protobuf.Timestamp
13, // 18: identity.EntityStorageEntry.mfa_secrets:type_name -> identity.EntityStorageEntry.MFASecretsEntry
14, // 19: identity.PersonaIndexEntry.metadata:type_name -> identity.PersonaIndexEntry.MetadataEntry
15, // 20: identity.PersonaIndexEntry.creation_time:type_name -> google.protobuf.Timestamp
15, // 21: identity.PersonaIndexEntry.last_update_time:type_name -> google.protobuf.Timestamp
16, // 22: identity.Entity.MFASecretsEntry.value:type_name -> mfa.Secret
16, // 23: identity.EntityStorageEntry.MFASecretsEntry.value:type_name -> mfa.Secret
24, // [24:24] is the sub-list for method output_type
24, // [24:24] is the sub-list for method input_type
24, // [24:24] is the sub-list for extension type_name
@ -1150,7 +1305,7 @@ func file_helper_identity_types_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_helper_identity_types_proto_rawDesc), len(file_helper_identity_types_proto_rawDesc)),
NumEnums: 0,
NumMessages: 14,
NumMessages: 15,
NumExtensions: 0,
NumServices: 0,
},

View file

@ -75,6 +75,13 @@ message Group {
// group.
// @inject_tag: sentinel:"-"
string namespace_id = 13;
// ScimClientId records the unique identifier of the SCIM client that
// originally created this Group. This establishes a strict ownership model,
// ensuring that authoritative lifecycle operations (like updates and deletes by
// an IGA) can only be performed by the client that owns the resource.
// @inject_tag: sentinel:"-"
string scim_client_id = 14;
}
// LocalAliases holds the aliases belonging to an entity that are local to the
@ -155,6 +162,21 @@ message Entity {
// entity.
// @inject_tag: sentinel:"-"
string namespace_id = 12;
// ExternalId stores the `externalId` provided by a SCIM client.
// This field serves as the canonical correlation key, linking this Vault Entity
// to its representation in the external identity management system. It is
// crucial for preventing the creation of duplicate entities when multiple
// SCIM clients provision the same user.
// @inject_tag: sentinel:"-"
string external_id = 13;
// ScimClientId records the unique identifier of the SCIM client that
// originally created this Entity. This establishes a strict ownership model,
// ensuring that authoritative lifecycle operations (like updates and deletes by
// an IGA) can only be performed by the client that owns the resource.
// @inject_tag: sentinel:"-"
string scim_client_id = 14;
}
// Alias represents the alias that gets stored inside of the
@ -233,6 +255,36 @@ message Alias {
// during invalidation of local aliases in performance standbys.
// @inject_tag: sentinel:"-"
string local_bucket_key = 14;
// ScimClientId records the unique identifier of the SCIM client that
// originally created this Alias. This establishes a strict ownership model,
// ensuring that authoritative lifecycle operations (like updates and deletes by
// an IGA) can only be performed by the client that owns the resource.
// @inject_tag: sentinel:"-"
string scim_client_id = 15;
}
// ScimConfig defines the stored configuration for a single SCIM client.
// This configuration links a client's identity within Vault to its specific
// role and capabilities within the SCIM server.
message ScimConfig {
// ClientId is a unique, user-defined identifier for this specific SCIM
// client configuration (e.g., 'Okta-Prod', 'SailPoint-Dev').
string client_id = 1;
// ClientRole defines the client's function and authoritative power.
// It must be either "IGA" (authoritative) or "IdP" (standard).
string client_role = 2;
// AccessGrantPrincipal is the Vault Entity ID that represents the SCIM
// client application itself. This is the principal that will be granted the
// necessary permissions to perform SCIM operations.
string access_grant_principal = 3;
// AliasMountAccessor is an optional field that specifies the mount accessor
// of an auth method where login aliases should be created for provisioned users.
// This is typically used for clients with the 'IdP' role.
string alias_mount_accessor = 4;
}
// Deprecated. Retained for backwards compatibility.

View file

@ -43,7 +43,10 @@ func (b *bufferedReader) Close() error {
return b.rOrig.Close()
}
const MergePatchContentTypeHeader = "application/merge-patch+json"
const (
MergePatchContentTypeHeader = "application/merge-patch+json"
ScimPatchContentTypeHeader = "application/scim+json"
)
func buildLogicalRequestNoAuth(perfStandby bool, ra *vault.RouterAccess, w http.ResponseWriter, r *http.Request) (*logical.Request, io.ReadCloser, int, error) {
ns, err := namespace.FromContext(r.Context())
@ -167,7 +170,7 @@ func buildLogicalRequestNoAuth(perfStandby bool, ra *vault.RouterAccess, w http.
return nil, nil, status, err
}
if contentType != MergePatchContentTypeHeader {
if contentType != MergePatchContentTypeHeader && contentType != ScimPatchContentTypeHeader {
return nil, nil, http.StatusUnsupportedMediaType, fmt.Errorf("PATCH requires Content-Type of %s, provided %s", MergePatchContentTypeHeader, contentType)
}

View file

@ -33,6 +33,7 @@ import (
const (
groupBucketsPrefix = "packer/group/buckets/"
localAliasesBucketsPrefix = "packer/local-aliases/buckets/"
scimBucketsPrefix = "packer/scim/buckets/"
)
var (
@ -95,6 +96,8 @@ func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendCo
core.AddLogger(localAliasesPackerLogger)
groupsPackerLogger := iStore.logger.Named("storagepacker").Named("groups")
core.AddLogger(groupsPackerLogger)
scimPackerLogger := iStore.logger.Named("storagepacker").Named("scim")
core.AddLogger(scimPackerLogger)
iStore.entityPacker, err = storagepacker.NewStoragePacker(iStore.view, entitiesPackerLogger, "")
if err != nil {
@ -111,6 +114,11 @@ func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendCo
return nil, fmt.Errorf("failed to create group packer: %w", err)
}
iStore.scimConfigPacker, err = storagepacker.NewStoragePacker(iStore.view, scimPackerLogger, scimBucketsPrefix)
if err != nil {
return nil, fmt.Errorf("failed to create scim packer: %w", err)
}
iStore.Backend = &framework.Backend{
BackendType: logical.TypeLogical,
Paths: iStore.paths(),
@ -123,7 +131,7 @@ func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendCo
"oidc/+/.well-known/*",
"oidc/provider/+/.well-known/*",
"oidc/provider/+/token",
}, identityStoreLoginMFAEntUnauthedPaths()...),
}),
LocalStorage: []string{
localAliasesBucketsPrefix,
},
@ -165,7 +173,6 @@ func (i *IdentityStore) paths() []*framework.Path {
mfaDuoPaths(i),
mfaPingIDPaths(i),
mfaLoginEnforcementPaths(i),
mfaLoginEnterprisePaths(i),
)
}

View file

@ -35,34 +35,7 @@ func aliasPaths(i *IdentityStore) []*framework.Path {
OperationSuffix: "alias",
},
Fields: map[string]*framework.FieldSchema{
"id": {
Type: framework.TypeString,
Description: "ID of the entity alias. If set, updates the corresponding entity alias.",
},
// entity_id is deprecated in favor of canonical_id
"entity_id": {
Type: framework.TypeString,
Description: `Entity ID to which this alias belongs.
This field is deprecated, use canonical_id.`,
},
"canonical_id": {
Type: framework.TypeString,
Description: "Entity ID to which this alias belongs",
},
"mount_accessor": {
Type: framework.TypeString,
Description: "Mount accessor to which this alias belongs to; unused for a modify",
},
"name": {
Type: framework.TypeString,
Description: "Name of the alias; unused for a modify",
},
"custom_metadata": {
Type: framework.TypeKVPairs,
Description: "User provided key-value pairs",
},
},
Fields: aliasFieldSchema(),
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: i.handleAliasCreateUpdate(),
},
@ -150,6 +123,37 @@ This field is deprecated, use canonical_id.`,
}
}
func aliasFieldSchema() map[string]*framework.FieldSchema {
return map[string]*framework.FieldSchema{
"id": {
Type: framework.TypeString,
Description: "ID of the entity alias. If set, updates the corresponding entity alias.",
},
// entity_id is deprecated in favor of canonical_id
"entity_id": {
Type: framework.TypeString,
Description: `Entity ID to which this alias belongs.
This field is deprecated, use canonical_id.`,
},
"canonical_id": {
Type: framework.TypeString,
Description: "Entity ID to which this alias belongs",
},
"mount_accessor": {
Type: framework.TypeString,
Description: "Mount accessor to which this alias belongs to; unused for a modify",
},
"name": {
Type: framework.TypeString,
Description: "Name of the alias; unused for a modify",
},
"custom_metadata": {
Type: framework.TypeKVPairs,
Description: "User provided key-value pairs",
},
}
}
// handleAliasCreateUpdate is used to create or update an alias
func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
@ -239,7 +243,6 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
return logical.ErrorResponse("'id' or 'mount_accessor' and 'name' must be provided"), nil
}
// Look up the alias by factors; if it's found it's an update
mountEntry := i.router.MatchingMountByAccessor(mountAccessor)
if mountEntry == nil {
return logical.ErrorResponse(fmt.Sprintf("invalid mount accessor %q", mountAccessor)), nil
@ -247,22 +250,30 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
if mountEntry.NamespaceID != ns.ID {
return logical.ErrorResponse("matching mount is in a different namespace than request"), logical.ErrPermissionDenied
}
alias, err := i.MemDBAliasByFactors(mountAccessor, name, true, false)
if err != nil {
return nil, err
}
if alias != nil {
if alias.NamespaceID != ns.ID {
return logical.ErrorResponse("cannot modify aliases across namespaces"), logical.ErrPermissionDenied
}
return i.handleAliasUpdate(ctx, canonicalID, name, mountAccessor, alias, customMetadata)
}
// At this point we know it's a new creation request
return i.handleAliasCreate(ctx, canonicalID, name, mountAccessor, mountEntry.Local, customMetadata)
localMount := mountEntry.Local
// Look up the alias by factors; if it's found it's an update
return i.handleAliasCreateUpdateCommon(ctx, ns, mountAccessor, name, canonicalID, customMetadata, localMount, "")
}
}
func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name, mountAccessor string, local bool, customMetadata map[string]string) (*logical.Response, error) {
func (i *IdentityStore) handleAliasCreateUpdateCommon(ctx context.Context, ns *namespace.Namespace, mountAccessor string, name string, canonicalID string, customMetadata map[string]string, localMount bool, scimClientID string) (*logical.Response, error) {
alias, err := i.MemDBAliasByFactors(mountAccessor, name, true, false)
if err != nil {
return nil, err
}
if alias != nil {
if alias.NamespaceID != ns.ID {
return logical.ErrorResponse("cannot modify aliases across namespaces"), logical.ErrPermissionDenied
}
return i.handleAliasUpdate(ctx, canonicalID, name, mountAccessor, alias, customMetadata)
}
// At this point we know it's a new creation request
return i.handleAliasCreate(ctx, canonicalID, name, mountAccessor, localMount, customMetadata, scimClientID)
}
func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name, mountAccessor string, local bool, customMetadata map[string]string, scimClientID string) (*logical.Response, error) {
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil, err
@ -326,6 +337,7 @@ func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name
Name: name,
CustomMetadata: customMetadata,
CanonicalID: entity.ID,
ScimClientID: scimClientID,
}
err = i.sanitizeAlias(ctx, alias)
if err != nil {

View file

@ -31,6 +31,14 @@ func entityPathFields() map[string]*framework.FieldSchema {
Type: framework.TypeString,
Description: "Name of the entity",
},
"external_id": {
Type: framework.TypeString,
Description: "External ID of the entity",
},
"scim_client_id": {
Type: framework.TypeString,
Description: "SCIM Client ID of the entity",
},
"metadata": {
Type: framework.TypeKVPairs,
Description: `Metadata to be associated with the entity.
@ -304,106 +312,7 @@ func (i *IdentityStore) handleEntityUpdateCommon() framework.OperationFunc {
i.lock.Lock()
defer i.lock.Unlock()
entity := new(identity.Entity)
var err error
entityID := d.Get("id").(string)
if entityID != "" {
entity, err = i.MemDBEntityByID(entityID, true)
if err != nil {
return nil, err
}
if entity == nil {
return logical.ErrorResponse("entity not found from id"), nil
}
}
// Get the name
entityName := d.Get("name").(string)
if entityName != "" {
entityByName, err := i.MemDBEntityByName(ctx, entityName, true)
if err != nil {
return nil, err
}
switch {
case entityByName == nil:
// Not found, safe to use this name with an existing or new entity
case entity.ID == "":
// Entity by ID was not found, but and entity for the supplied
// name was found. Continue updating the entity.
entity = entityByName
case entity.ID == entityByName.ID:
// Same exact entity, carry on (this is basically a noop then)
default:
return logical.ErrorResponse("entity name is already in use"), nil
}
}
if entityName != "" {
entity.Name = entityName
}
// Update the policies if supplied
entityPoliciesRaw, ok := d.GetOk("policies")
if ok {
entity.Policies = strutil.RemoveDuplicates(entityPoliciesRaw.([]string), false)
}
if strutil.StrListContainsCaseInsensitive(entity.Policies, "root") {
return logical.ErrorResponse("policies cannot contain root"), nil
}
disabledRaw, ok := d.GetOk("disabled")
if ok {
entity.Disabled = disabledRaw.(bool)
}
// Get entity metadata
metadata, ok, err := d.GetOkErr("metadata")
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("failed to parse metadata: %v", err)), nil
}
if ok {
entity.Metadata = metadata.(map[string]string)
}
// At this point, if entity.ID is empty, it indicates that a new entity
// is being created. Using this to respond data in the response.
newEntity := entity.ID == ""
// ID creation and some validations
err = i.sanitizeEntity(ctx, entity)
if err != nil {
return nil, err
}
if err := i.upsertEntity(ctx, entity, nil, true); err != nil {
return nil, err
}
// If this operation was an update to an existing entity, return 204
if !newEntity {
return nil, nil
}
// Prepare the response
respData := map[string]interface{}{
"id": entity.ID,
"name": entity.Name,
}
var aliasIDs []string
for _, alias := range entity.Aliases {
aliasIDs = append(aliasIDs, alias.ID)
}
respData["aliases"] = aliasIDs
// Return ID of the entity that was either created or updated along with
// its aliases
return &logical.Response{
Data: respData,
}, nil
return i.EntityUpdateCommon(ctx, d)
}
}

View file

@ -0,0 +1,249 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package vault
import (
"context"
"fmt"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/vault/sdk/logical"
)
// EntityBuilder is used to construct or update an identity.Entity.
type EntityBuilder struct {
store *IdentityStore
entity *identity.Entity
isNew bool
err error
}
// NewEntityBuilder creates a new builder instance.
func NewEntityBuilder(store *IdentityStore) *EntityBuilder {
return &EntityBuilder{
store: store,
entity: new(identity.Entity),
isNew: true, // Assume new until an existing entity is loaded
}
}
// WithExistingEntity allows you to pass in a preloaded entity.
func (b *EntityBuilder) WithExistingEntity(entity *identity.Entity) *EntityBuilder {
if b.err != nil || entity == nil {
return b
}
b.entity = entity
b.isNew = false
return b
}
// WithID attempts to load an entity by its ID.
func (b *EntityBuilder) WithID(id string) *EntityBuilder {
if b.err != nil || id == "" {
return b
}
entity, err := b.store.MemDBEntityByID(id, true)
if err != nil {
b.err = err
return b
}
if entity == nil {
b.err = fmt.Errorf("entity not found from id: %s", id)
return b
}
b.entity = entity
b.isNew = false
return b
}
// WithExternalID handles logic related to the external_id.
func (b *EntityBuilder) WithExternalID(ctx context.Context, externalID string) *EntityBuilder {
if b.err != nil || externalID == "" {
return b
}
entityByExternalID, err := b.store.MemDBEntityByExternalID(ctx, externalID, true)
if err != nil {
b.err = err
return b
}
if entityByExternalID != nil {
// An entity with this external ID already exists, so we'll update it.
b.entity = entityByExternalID
b.isNew = false
} else {
// No entity found, so we're just setting the external ID on the current one.
b.entity.ExternalID = externalID
}
return b
}
// WithName handles the complex logic for finding an entity by name and checking for conflicts.
func (b *EntityBuilder) WithName(ctx context.Context, name string) *EntityBuilder {
if b.err != nil || name == "" {
return b
}
entityByName, err := b.store.MemDBEntityByName(ctx, name, true)
if err != nil {
b.err = err
return b
}
switch {
case entityByName == nil:
// Not found, safe to use this name.
case b.isNew:
// We haven't loaded an entity yet, but one with this name exists. Let's update it.
b.entity = entityByName
b.isNew = false
case b.entity.ID == entityByName.ID:
// The loaded entity and the one found by name are the same. No-op.
default:
// A different entity already has this name, which is a conflict.
b.err = fmt.Errorf("entity name '%s' is already in use", name)
return b
}
b.entity.Name = name
return b
}
// WithPolicies sets the policies for the entity.
func (b *EntityBuilder) WithPolicies(policies []string) *EntityBuilder {
if b.err != nil {
return b
}
if strutil.StrListContainsCaseInsensitive(policies, "root") {
b.err = fmt.Errorf("policies cannot contain root")
return b
}
b.entity.Policies = strutil.RemoveDuplicates(policies, false)
return b
}
// WithDisabled sets the disabled status of the entity.
func (b *EntityBuilder) WithDisabled(disabled bool) *EntityBuilder {
if b.err != nil {
return b
}
b.entity.Disabled = disabled
return b
}
// WithMetadata sets the metadata for the entity.
func (b *EntityBuilder) WithMetadata(metadata map[string]string) *EntityBuilder {
if b.err != nil {
return b
}
b.entity.Metadata = metadata
return b
}
// WithSCIMClientID sets the SCIM client ID.
func (b *EntityBuilder) WithSCIMClientID(scimClientID string) *EntityBuilder {
if b.err != nil {
return b
}
b.entity.ScimClientID = scimClientID
return b
}
// Build finalizes the entity creation/update process.
func (b *EntityBuilder) Build(ctx context.Context) (*logical.Response, error) {
// If any previous step set an error, return it immediately.
if b.err != nil {
return logical.ErrorResponse(b.err.Error()), nil
}
// Sanitize and persist the entity
if err := b.store.sanitizeEntity(ctx, b.entity); err != nil {
return nil, err
}
if err := b.store.upsertEntity(ctx, b.entity, nil, true); err != nil {
return nil, err
}
// If this was an update to an existing entity, return 204 No Content
if !b.isNew {
return &logical.Response{}, nil
}
// This was a new entity, so prepare the response data
respData := map[string]interface{}{
"id": b.entity.ID,
"name": b.entity.Name,
}
var aliasIDs []string
for _, alias := range b.entity.Aliases {
aliasIDs = append(aliasIDs, alias.ID)
}
respData["aliases"] = aliasIDs
return &logical.Response{
Data: respData,
}, nil
}
// FromFieldData is a convenience method to populate the builder from a FieldData object.
func (b *EntityBuilder) FromFieldData(ctx context.Context, d *framework.FieldData) *EntityBuilder {
if b.err != nil {
return b
}
if id, ok := d.GetOk("id"); ok {
b.WithID(id.(string))
}
if externalID, ok := d.GetOk("external_id"); ok {
b.WithExternalID(ctx, externalID.(string))
}
if name, ok := d.GetOk("name"); ok {
b.WithName(ctx, name.(string))
}
if scimClientID, ok := d.GetOk("scim_client_id"); ok {
b.WithSCIMClientID(scimClientID.(string))
}
if policies, ok := d.GetOk("policies"); ok {
b.WithPolicies(policies.([]string))
}
if disabled, ok := d.GetOk("disabled"); ok {
b.WithDisabled(disabled.(bool))
}
if metadata, ok, err := d.GetOkErr("metadata"); err != nil {
b.err = fmt.Errorf("failed to parse metadata: %v", err)
} else if ok {
b.WithMetadata(metadata.(map[string]string))
}
return b
}
// Upsert finalizes the entity update process by persisting it to storage.
func (b *EntityBuilder) Upsert(ctx context.Context) (*identity.Entity, error) {
if b.err != nil {
return nil, b.err
}
// Sanitize and persist the entity
if err := b.store.sanitizeEntity(ctx, b.entity); err != nil {
return nil, err
}
if err := b.store.upsertEntity(ctx, b.entity, nil, true); err != nil {
return nil, err
}
return b.entity, nil
}
func (i *IdentityStore) EntityUpdateCommon(ctx context.Context, d *framework.FieldData) (*logical.Response, error) {
return NewEntityBuilder(i).
FromFieldData(ctx, d).
Build(ctx)
}

View file

@ -35,6 +35,10 @@ func groupPathFields() map[string]*framework.FieldSchema {
Type: framework.TypeString,
Description: "Name of the group.",
},
"scim_client_id": {
Type: framework.TypeString,
Description: "SCIM Client ID of the entity",
},
"metadata": {
Type: framework.TypeKVPairs,
Description: `Metadata to be associated with the group.
@ -309,6 +313,12 @@ func (i *IdentityStore) handleGroupUpdateCommon(ctx context.Context, req *logica
group.Name = groupName
}
_, ok = d.Schema["scim_client_id"]
if ok {
entitSCIMClientID := d.Get("scim_client_id").(string)
group.ScimClientID = entitSCIMClientID
}
metadata, ok, err := d.GetOkErr("metadata")
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("failed to parse metadata: %v", err)), nil

View file

@ -28,6 +28,7 @@ func identityStoreSchema(lowerCaseName bool) *memdb.DBSchema {
groupsTableSchema,
groupAliasesTableSchema,
oidcClientsTableSchema,
scimConfigSchema,
}
for _, schemaFunc := range schemas {
@ -130,6 +131,33 @@ func entitiesTableSchema(lowerCaseName bool) *memdb.TableSchema {
Field: "NamespaceID",
},
},
// SCIM-related indexes
"external_id": {
Name: "external_id",
Unique: true,
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "NamespaceID",
},
&memdb.StringFieldIndex{
Field: "ExternalID",
},
},
},
AllowMissing: true,
},
"scim_client_id": {
Name: "scim_client_id",
AllowMissing: true,
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{Field: "NamespaceID"},
&memdb.StringFieldIndex{Field: "ScimClientID"},
},
},
},
},
}
}
@ -186,6 +214,13 @@ func groupsTableSchema(lowerCaseName bool) *memdb.TableSchema {
Field: "NamespaceID",
},
},
"scim_client_id": {
Name: "scim_client_id",
Indexer: &memdb.StringFieldIndex{
Field: "ScimClientID",
},
AllowMissing: true,
},
},
}
}

View file

@ -0,0 +1,59 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package vault
import "github.com/hashicorp/go-memdb"
const (
scimConfigTable = "scim_config"
)
func scimConfigSchema(_ bool) *memdb.TableSchema {
return &memdb.TableSchema{
Name: scimConfigTable,
Indexes: map[string]*memdb.IndexSchema{
"id": {
Name: "id",
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "ID",
},
},
"client_id": {
Name: "client_id",
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "ClientID",
},
},
"client_role": {
Name: "client_role",
Indexer: &memdb.StringFieldIndex{
Field: "ClientRole",
},
},
"access_grant_method": {
Name: "access_grant_method",
Indexer: &memdb.StringFieldIndex{
Field: "AccessGrantMethod",
},
},
"access_grant_principal": {
Name: "access_grant_principal",
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "AccessGrantPrincipal",
},
},
"alias_mount_accessor": {
Name: "alias_mount_accessor",
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "AliasMountAccessor",
},
AllowMissing: true,
},
},
}
}

View file

@ -100,6 +100,10 @@ type IdentityStore struct {
// buckets
groupPacker *storagepacker.StoragePacker
// groupPacker is used to pack multiple group storage entries into 256
// buckets
scimConfigPacker *storagepacker.StoragePacker
// disableLowerCaseNames indicates whether or not identity artifacts are
// operated case insensitively
disableLowerCasedNames bool

View file

@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"math/rand"
"sort"
"strings"
"sync"
"testing"
@ -1467,6 +1468,42 @@ func (i *IdentityStore) MemDBEntityByIDInTxn(txn *memdb.Txn, entityID string, cl
return entity, nil
}
func (i *IdentityStore) MemDBEntityByExternalIDInTxn(ctx context.Context, txn *memdb.Txn, externalID string, clone bool) (*identity.Entity, error) {
var entity *identity.Entity
if externalID == "" {
return nil, fmt.Errorf("missing entity id")
}
if txn == nil {
return nil, fmt.Errorf("txn is nil")
}
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil, err
}
entityRaw, err := txn.First(entitiesTable, "external_id", ns.ID, externalID)
if err != nil {
return nil, fmt.Errorf("failed to fetch entity from memdb using entity id: %w", err)
}
if entityRaw == nil {
return entity, nil
}
entity, ok := entityRaw.(*identity.Entity)
if !ok {
return nil, fmt.Errorf("failed to declare the type of fetched entity")
}
if clone {
return entity.Clone()
}
return entity, nil
}
func (i *IdentityStore) MemDBEntityByID(entityID string, clone bool) (*identity.Entity, error) {
if entityID == "" {
return nil, fmt.Errorf("missing entity id")
@ -1477,6 +1514,16 @@ func (i *IdentityStore) MemDBEntityByID(entityID string, clone bool) (*identity.
return i.MemDBEntityByIDInTxn(txn, entityID, clone)
}
func (i *IdentityStore) MemDBEntityByExternalID(ctx context.Context, externalID string, clone bool) (*identity.Entity, error) {
if externalID == "" {
return nil, fmt.Errorf("missing entity externalID")
}
txn := i.db.Txn(false)
return i.MemDBEntityByExternalIDInTxn(ctx, txn, externalID, clone)
}
func (i *IdentityStore) MemDBEntityByName(ctx context.Context, entityName string, clone bool) (*identity.Entity, error) {
if entityName == "" {
return nil, fmt.Errorf("missing entity name")
@ -1659,6 +1706,98 @@ func (i *IdentityStore) MemDBDeleteEntityByID(entityID string) error {
return nil
}
func (i *IdentityStore) MemDBEntitiesByScimClientID(ctx context.Context, scimClientID string, maxResultSet int) ([]*identity.Entity, error) {
if scimClientID == "" {
return nil, nil
}
txn := i.db.Txn(false)
defer txn.Abort()
entities, err := i.MemDBEntitiesByScimClientIDInTxn(ctx, txn, scimClientID, maxResultSet)
if err != nil {
return nil, err
}
return entities, nil
}
// MemDBEntitiesByScimClientIDInTxn retrieves a fully sorted list of all entities
// belonging to a specific SCIM client.
//
// Implementation Pattern: Filter-Then-Sort
// This function implements the "filter then sort" pattern, which is the idiomatic
// approach for this type of query in go-memdb. The process involves two steps:
// 1. Filtering: A simple two-part compound index on `(NamespaceID, ScimClientID)`
// is used with `txn.Get()` to efficiently retrieve all entities for the client.
// 2. Sorting: The resulting slice of entities is then sorted in-memory by name
// using Go's native `sort.Slice`.
//
// Why This Pattern is Used (memdb Limitations):
// It might seem more efficient to perform the sorting at the database level
// using a single ordered index, memdb's built-in `CompoundIndex` does not support
// using a partial key (e.g., just `NamespaceID` and `ScimClientID`) to get an
// ordered iterator over the remaining fields (e.g., `Name`). The indexer's
// internal `FromArgs` method requires a value for every field defined in the index.
// This makes a single-operation "filter and ordered scan" impossible with the
// standard indexers.
//
// Given that go-memdb is an in-memory database, sorting a pre-filtered slice
// is extremely fast and avoids the complexity of creating a custom indexer.
// This function returns the entire sorted list; the caller is responsible for
// any subsequent pagination.
func (i *IdentityStore) MemDBEntitiesByScimClientIDInTxn(ctx context.Context, txn *memdb.Txn, scimClientID string, maxResultSet int) ([]*identity.Entity, error) {
if txn == nil {
return nil, fmt.Errorf("nil txn")
}
if scimClientID == "" {
return nil, fmt.Errorf("empty scim client id key")
}
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil, err
}
ws := memdb.NewWatchSet()
entitiesIter, err := txn.Get(entitiesTable, "scim_client_id", ns.ID, scimClientID)
if err != nil {
return nil, fmt.Errorf("failed to lookup entities using scim client id: %w", err)
}
ws.Add(entitiesIter.WatchCh())
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
break
}
var entities []*identity.Entity
for item := entitiesIter.Next(); item != nil; item = entitiesIter.Next() {
if len(entities) > maxResultSet {
return nil, fmt.Errorf("query returned more than the server's limit of %d results. Please use more specific filters", maxResultSet)
}
entity, err := item.(*identity.Entity).Clone()
if err != nil {
return nil, err
}
entities = append(entities, entity)
}
sort.Slice(entities, func(i, j int) bool {
return entities[i].Name < entities[j].Name
})
return entities, nil
}
}
// FetchEntityForLocalAliasInTxn fetches the entity associated with the provided
// local identity.Alias. MemDB will first be searched for the entity. If it is
// not found there, the localAliasPacker storagepacker.StoragePacker will be
@ -2344,6 +2483,43 @@ func (i *IdentityStore) MemDBGroupByID(groupID string, clone bool) (*identity.Gr
return i.MemDBGroupByIDInTxn(txn, groupID, clone)
}
func (i *IdentityStore) MemDBGroupsByScimClientIDInTxn(txn *memdb.Txn, scimClientID string) ([]*identity.Group, error) {
if scimClientID == "" {
return nil, fmt.Errorf("missing scim client ID")
}
if txn == nil {
return nil, fmt.Errorf("txn is nil")
}
groupsIter, err := txn.Get(groupsTable, "scim_client_id", scimClientID)
if err != nil {
return nil, fmt.Errorf("failed to lookup groups using scim client ID: %w", err)
}
var groups []*identity.Group
for group := groupsIter.Next(); group != nil; group = groupsIter.Next() {
entry := group.(*identity.Group)
entry, err = entry.Clone()
if err != nil {
return nil, err
}
groups = append(groups, entry)
}
return groups, nil
}
func (i *IdentityStore) MemDBGroupsByScimClientID(scimClientID string) ([]*identity.Group, error) {
if scimClientID == "" {
return nil, fmt.Errorf("missing scim client ID")
}
txn := i.db.Txn(false)
return i.MemDBGroupsByScimClientIDInTxn(txn, scimClientID)
}
func (i *IdentityStore) MemDBGroupsByParentGroupIDInTxn(txn *memdb.Txn, memberGroupID string, clone bool) ([]*identity.Group, error) {
if memberGroupID == "" {
return nil, fmt.Errorf("missing member group ID")

View file

@ -18,6 +18,13 @@ func namespaceByID(ctx context.Context, nsID string, c *Core) (*namespace.Namesp
return nil, namespace.ErrNoNamespace
}
func namespaceByPath(ctx context.Context, nsPath string, c *Core) (*namespace.Namespace, error) {
if nsPath == "" {
return namespace.RootNamespace, nil
}
return nil, namespace.ErrNoNamespace
}
var NamespaceRegister func(context.Context, *namespace.Namespace, *Core) error = namespaceRegister
func namespaceRegister(ctx context.Context, ns *namespace.Namespace, c *Core) error {