From f9907d31b50c09f81c6109740db7f48e5b93d624 Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:42:32 -0500 Subject: [PATCH] [v17] [Web] Implement periodic check to notify access list owners of reviews due soon (#51713) * add access list reminder notification type (#51426) * periodically create access list reminder notifications when needed (#51581) --- .../notifications/v1/notifications.pb.go | 421 ++++++++++-------- .../notifications/v1/notifications.proto | 8 + api/types/constants.go | 33 ++ api/types/semaphore.go | 4 + lib/auth/auth.go | 242 ++++++++++ lib/auth/auth_test.go | 105 +++++ lib/auth/notification_test.go | 30 +- .../notifications/notificationsv1/service.go | 4 + .../src/Notifications/Notifications.tsx | 1 + .../teleport/src/Notifications/fixtures.ts | 48 ++ .../src/services/notifications/types.ts | 11 + 11 files changed, 722 insertions(+), 185 deletions(-) diff --git a/api/gen/proto/go/teleport/notifications/v1/notifications.pb.go b/api/gen/proto/go/teleport/notifications/v1/notifications.pb.go index 8f209475feaea..8860d660c2a9c 100644 --- a/api/gen/proto/go/teleport/notifications/v1/notifications.pb.go +++ b/api/gen/proto/go/teleport/notifications/v1/notifications.pb.go @@ -336,6 +336,7 @@ type GlobalNotificationSpec struct { // *GlobalNotificationSpec_ByPermissions // *GlobalNotificationSpec_ByRoles // *GlobalNotificationSpec_All + // *GlobalNotificationSpec_ByUsers Matcher isGlobalNotificationSpec_Matcher `protobuf_oneof:"matcher"` // match_all_conditions is whether or not all the conditions specified by the matcher must be met, // if false, only one of the conditions needs to be met. @@ -405,6 +406,13 @@ func (x *GlobalNotificationSpec) GetAll() bool { return false } +func (x *GlobalNotificationSpec) GetByUsers() *ByUsers { + if x, ok := x.GetMatcher().(*GlobalNotificationSpec_ByUsers); ok { + return x.ByUsers + } + return nil +} + func (x *GlobalNotificationSpec) GetMatchAllConditions() bool { if x != nil { return x.MatchAllConditions @@ -449,12 +457,20 @@ type GlobalNotificationSpec_All struct { All bool `protobuf:"varint,3,opt,name=all,proto3,oneof"` } +type GlobalNotificationSpec_ByUsers struct { + // by_users represents a list of usernames of the users targeted by this notification. + // If only one user is being targeted, please create a user-specific notification instead. + ByUsers *ByUsers `protobuf:"bytes,7,opt,name=by_users,json=byUsers,proto3,oneof"` +} + func (*GlobalNotificationSpec_ByPermissions) isGlobalNotificationSpec_Matcher() {} func (*GlobalNotificationSpec_ByRoles) isGlobalNotificationSpec_Matcher() {} func (*GlobalNotificationSpec_All) isGlobalNotificationSpec_Matcher() {} +func (*GlobalNotificationSpec_ByUsers) isGlobalNotificationSpec_Matcher() {} + // ByPermissions represents the RoleConditions needed for a user to receive this notification. type ByPermissions struct { state protoimpl.MessageState @@ -547,6 +563,52 @@ func (x *ByRoles) GetRoles() []string { return nil } +// ByUsers represents the users targeted by this notification. +type ByUsers struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Users []string `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` +} + +func (x *ByUsers) Reset() { + *x = ByUsers{} + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ByUsers) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ByUsers) ProtoMessage() {} + +func (x *ByUsers) ProtoReflect() protoreflect.Message { + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[6] + 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 ByUsers.ProtoReflect.Descriptor instead. +func (*ByUsers) Descriptor() ([]byte, []int) { + return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{6} +} + +func (x *ByUsers) GetUsers() []string { + if x != nil { + return x.Users + } + return nil +} + // UserNotificationState represents a notification's state for a user. This is to keep track // of whether the user has clicked on or dismissed the notification. type UserNotificationState struct { @@ -570,7 +632,7 @@ type UserNotificationState struct { func (x *UserNotificationState) Reset() { *x = UserNotificationState{} - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[6] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -582,7 +644,7 @@ func (x *UserNotificationState) String() string { func (*UserNotificationState) ProtoMessage() {} func (x *UserNotificationState) ProtoReflect() protoreflect.Message { - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[6] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -595,7 +657,7 @@ func (x *UserNotificationState) ProtoReflect() protoreflect.Message { // Deprecated: Use UserNotificationState.ProtoReflect.Descriptor instead. func (*UserNotificationState) Descriptor() ([]byte, []int) { - return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{6} + return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{7} } func (x *UserNotificationState) GetKind() string { @@ -654,7 +716,7 @@ type UserNotificationStateSpec struct { func (x *UserNotificationStateSpec) Reset() { *x = UserNotificationStateSpec{} - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[7] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -666,7 +728,7 @@ func (x *UserNotificationStateSpec) String() string { func (*UserNotificationStateSpec) ProtoMessage() {} func (x *UserNotificationStateSpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[7] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -679,7 +741,7 @@ func (x *UserNotificationStateSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use UserNotificationStateSpec.ProtoReflect.Descriptor instead. func (*UserNotificationStateSpec) Descriptor() ([]byte, []int) { - return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{7} + return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{8} } func (x *UserNotificationStateSpec) GetNotificationId() string { @@ -708,7 +770,7 @@ type UserNotificationStateStatus struct { func (x *UserNotificationStateStatus) Reset() { *x = UserNotificationStateStatus{} - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[8] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -720,7 +782,7 @@ func (x *UserNotificationStateStatus) String() string { func (*UserNotificationStateStatus) ProtoMessage() {} func (x *UserNotificationStateStatus) ProtoReflect() protoreflect.Message { - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[8] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -733,7 +795,7 @@ func (x *UserNotificationStateStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use UserNotificationStateStatus.ProtoReflect.Descriptor instead. func (*UserNotificationStateStatus) Descriptor() ([]byte, []int) { - return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{8} + return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{9} } func (x *UserNotificationStateStatus) GetNotificationState() NotificationState { @@ -765,7 +827,7 @@ type UserLastSeenNotification struct { func (x *UserLastSeenNotification) Reset() { *x = UserLastSeenNotification{} - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[9] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -777,7 +839,7 @@ func (x *UserLastSeenNotification) String() string { func (*UserLastSeenNotification) ProtoMessage() {} func (x *UserLastSeenNotification) ProtoReflect() protoreflect.Message { - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[9] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -790,7 +852,7 @@ func (x *UserLastSeenNotification) ProtoReflect() protoreflect.Message { // Deprecated: Use UserLastSeenNotification.ProtoReflect.Descriptor instead. func (*UserLastSeenNotification) Descriptor() ([]byte, []int) { - return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{9} + return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{10} } func (x *UserLastSeenNotification) GetKind() string { @@ -844,7 +906,7 @@ type UserLastSeenNotificationSpec struct { func (x *UserLastSeenNotificationSpec) Reset() { *x = UserLastSeenNotificationSpec{} - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[10] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -856,7 +918,7 @@ func (x *UserLastSeenNotificationSpec) String() string { func (*UserLastSeenNotificationSpec) ProtoMessage() {} func (x *UserLastSeenNotificationSpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[10] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -869,7 +931,7 @@ func (x *UserLastSeenNotificationSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use UserLastSeenNotificationSpec.ProtoReflect.Descriptor instead. func (*UserLastSeenNotificationSpec) Descriptor() ([]byte, []int) { - return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{10} + return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{11} } // UserLastSeenNotificationStatus is the timestamp of this user's last seen notification, it contains the timestamp of the notification which will be dynamically modified. @@ -884,7 +946,7 @@ type UserLastSeenNotificationStatus struct { func (x *UserLastSeenNotificationStatus) Reset() { *x = UserLastSeenNotificationStatus{} - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[11] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -896,7 +958,7 @@ func (x *UserLastSeenNotificationStatus) String() string { func (*UserLastSeenNotificationStatus) ProtoMessage() {} func (x *UserLastSeenNotificationStatus) ProtoReflect() protoreflect.Message { - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[11] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -909,7 +971,7 @@ func (x *UserLastSeenNotificationStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use UserLastSeenNotificationStatus.ProtoReflect.Descriptor instead. func (*UserLastSeenNotificationStatus) Descriptor() ([]byte, []int) { - return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{11} + return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{12} } func (x *UserLastSeenNotificationStatus) GetLastSeenTime() *timestamppb.Timestamp { @@ -942,7 +1004,7 @@ type UniqueNotificationIdentifier struct { func (x *UniqueNotificationIdentifier) Reset() { *x = UniqueNotificationIdentifier{} - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[12] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -954,7 +1016,7 @@ func (x *UniqueNotificationIdentifier) String() string { func (*UniqueNotificationIdentifier) ProtoMessage() {} func (x *UniqueNotificationIdentifier) ProtoReflect() protoreflect.Message { - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[12] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -967,7 +1029,7 @@ func (x *UniqueNotificationIdentifier) ProtoReflect() protoreflect.Message { // Deprecated: Use UniqueNotificationIdentifier.ProtoReflect.Descriptor instead. func (*UniqueNotificationIdentifier) Descriptor() ([]byte, []int) { - return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{12} + return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{13} } func (x *UniqueNotificationIdentifier) GetKind() string { @@ -1012,7 +1074,7 @@ type UniqueNotificationIdentifierSpec struct { func (x *UniqueNotificationIdentifierSpec) Reset() { *x = UniqueNotificationIdentifierSpec{} - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[13] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1024,7 +1086,7 @@ func (x *UniqueNotificationIdentifierSpec) String() string { func (*UniqueNotificationIdentifierSpec) ProtoMessage() {} func (x *UniqueNotificationIdentifierSpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[13] + mi := &file_teleport_notifications_v1_notifications_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1037,7 +1099,7 @@ func (x *UniqueNotificationIdentifierSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use UniqueNotificationIdentifierSpec.ProtoReflect.Descriptor instead. func (*UniqueNotificationIdentifierSpec) Descriptor() ([]byte, []int) { - return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{13} + return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{14} } func (x *UniqueNotificationIdentifierSpec) GetUniqueIdentifier() string { @@ -1104,7 +1166,7 @@ var file_teleport_notifications_v1_notifications_proto_rawDesc = []byte{ 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, - 0x73, 0x70, 0x65, 0x63, 0x22, 0xef, 0x02, 0x0a, 0x16, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, + 0x73, 0x70, 0x65, 0x63, 0x22, 0xb0, 0x03, 0x0a, 0x16, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x12, 0x51, 0x0a, 0x0e, 0x62, 0x79, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, @@ -1116,124 +1178,130 @@ var file_teleport_notifications_v1_notifications_proto_rawDesc = []byte{ 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x79, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x48, 0x00, 0x52, 0x07, 0x62, 0x79, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x48, 0x00, 0x52, 0x03, 0x61, 0x6c, 0x6c, 0x12, 0x30, 0x0a, 0x14, 0x6d, 0x61, 0x74, 0x63, 0x68, - 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x41, 0x6c, 0x6c, 0x43, - 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x4b, 0x0a, 0x0c, 0x6e, 0x6f, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, - 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x65, - 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x6d, - 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x4f, 0x0a, 0x0d, 0x42, 0x79, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3e, 0x0a, 0x0f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, - 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x6f, 0x6e, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0e, 0x72, 0x6f, 0x6c, 0x65, 0x43, 0x6f, 0x6e, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x1f, 0x0a, 0x07, 0x42, 0x79, 0x52, 0x6f, 0x6c, - 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x22, 0xb4, 0x02, 0x0a, 0x15, 0x55, 0x73, 0x65, - 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x75, 0x62, 0x5f, 0x6b, 0x69, - 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x4b, 0x69, 0x6e, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x48, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, - 0x4e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x36, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, - 0x60, 0x0a, 0x19, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x27, 0x0a, 0x0f, - 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, - 0x65, 0x22, 0x7a, 0x0a, 0x1b, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x5b, 0x0a, 0x12, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x74, + 0x48, 0x00, 0x52, 0x03, 0x61, 0x6c, 0x6c, 0x12, 0x3f, 0x0a, 0x08, 0x62, 0x79, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, + 0x07, 0x62, 0x79, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x6d, 0x61, 0x74, 0x63, + 0x68, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x41, 0x6c, 0x6c, + 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x4b, 0x0a, 0x0c, 0x6e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x6e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, + 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x42, 0x09, 0x0a, 0x07, + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x4f, 0x0a, 0x0d, 0x42, 0x79, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3e, 0x0a, 0x0f, 0x72, 0x6f, 0x6c, 0x65, + 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0e, 0x72, 0x6f, 0x6c, 0x65, 0x43, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x1f, 0x0a, 0x07, 0x42, 0x79, 0x52, 0x6f, + 0x6c, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x22, 0x1f, 0x0a, 0x07, 0x42, 0x79, 0x55, + 0x73, 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0xb4, 0x02, 0x0a, 0x15, 0x55, + 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x75, 0x62, 0x5f, + 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x4b, + 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x48, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, + 0x63, 0x12, 0x4e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x36, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, + 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x22, 0x60, 0x0a, 0x19, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x27, + 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, + 0x61, 0x6d, 0x65, 0x22, 0x7a, 0x0a, 0x1b, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x5b, 0x0a, 0x12, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x11, 0x6e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, + 0xc9, 0x02, 0x0a, 0x18, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, + 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x73, 0x75, 0x62, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, + 0x4b, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x61, + 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x51, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x11, 0x6e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0xc9, 0x02, - 0x0a, 0x18, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, - 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x19, - 0x0a, 0x08, 0x73, 0x75, 0x62, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x73, 0x75, 0x62, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4b, 0x0a, - 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, - 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x51, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, - 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a, 0x04, 0x08, - 0x06, 0x10, 0x07, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x1e, 0x0a, 0x1c, 0x55, 0x73, 0x65, - 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x22, 0x62, 0x0a, 0x1e, 0x55, 0x73, 0x65, - 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x40, 0x0a, 0x0e, 0x6c, - 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 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, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xd7, 0x01, - 0x0a, 0x1c, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x12, - 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, - 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4f, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x53, 0x70, 0x65, - 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x89, 0x01, 0x0a, 0x20, 0x55, 0x6e, 0x69, 0x71, - 0x75, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x53, 0x70, 0x65, 0x63, 0x12, 0x2b, 0x0a, 0x11, - 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x49, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x18, 0x75, 0x6e, 0x69, - 0x71, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x5f, 0x70, - 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x75, 0x6e, 0x69, - 0x71, 0x75, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x50, 0x72, 0x65, - 0x66, 0x69, 0x78, 0x2a, 0x79, 0x0a, 0x11, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x1e, 0x4e, 0x4f, 0x54, 0x49, - 0x46, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, - 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, - 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x5f, 0x43, 0x4c, 0x49, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, - 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x5f, 0x44, 0x49, 0x53, 0x4d, 0x49, 0x53, 0x53, 0x45, 0x44, 0x10, 0x02, 0x42, 0x5e, - 0x5a, 0x5c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, - 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x6e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x76, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, + 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a, + 0x04, 0x08, 0x06, 0x10, 0x07, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x1e, 0x0a, 0x1c, 0x55, + 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x22, 0x62, 0x0a, 0x1e, 0x55, + 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x40, 0x0a, + 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x01, 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, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x22, + 0xd7, 0x01, 0x0a, 0x1c, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, + 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4f, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x53, + 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x89, 0x01, 0x0a, 0x20, 0x55, 0x6e, + 0x69, 0x71, 0x75, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x53, 0x70, 0x65, 0x63, 0x12, 0x2b, + 0x0a, 0x11, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x6e, 0x69, 0x71, 0x75, + 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x18, 0x75, + 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x75, + 0x6e, 0x69, 0x71, 0x75, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x50, + 0x72, 0x65, 0x66, 0x69, 0x78, 0x2a, 0x79, 0x0a, 0x11, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x1e, 0x4e, 0x4f, + 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, + 0x0a, 0x1a, 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4c, 0x49, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x20, + 0x0a, 0x1c, 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x49, 0x53, 0x4d, 0x49, 0x53, 0x53, 0x45, 0x44, 0x10, 0x02, + 0x42, 0x5e, 0x5a, 0x5c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, + 0x3b, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x76, 0x31, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1249,7 +1317,7 @@ func file_teleport_notifications_v1_notifications_proto_rawDescGZIP() []byte { } var file_teleport_notifications_v1_notifications_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_teleport_notifications_v1_notifications_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_teleport_notifications_v1_notifications_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_teleport_notifications_v1_notifications_proto_goTypes = []any{ (NotificationState)(0), // 0: teleport.notifications.v1.NotificationState (*Notification)(nil), // 1: teleport.notifications.v1.Notification @@ -1258,43 +1326,45 @@ var file_teleport_notifications_v1_notifications_proto_goTypes = []any{ (*GlobalNotificationSpec)(nil), // 4: teleport.notifications.v1.GlobalNotificationSpec (*ByPermissions)(nil), // 5: teleport.notifications.v1.ByPermissions (*ByRoles)(nil), // 6: teleport.notifications.v1.ByRoles - (*UserNotificationState)(nil), // 7: teleport.notifications.v1.UserNotificationState - (*UserNotificationStateSpec)(nil), // 8: teleport.notifications.v1.UserNotificationStateSpec - (*UserNotificationStateStatus)(nil), // 9: teleport.notifications.v1.UserNotificationStateStatus - (*UserLastSeenNotification)(nil), // 10: teleport.notifications.v1.UserLastSeenNotification - (*UserLastSeenNotificationSpec)(nil), // 11: teleport.notifications.v1.UserLastSeenNotificationSpec - (*UserLastSeenNotificationStatus)(nil), // 12: teleport.notifications.v1.UserLastSeenNotificationStatus - (*UniqueNotificationIdentifier)(nil), // 13: teleport.notifications.v1.UniqueNotificationIdentifier - (*UniqueNotificationIdentifierSpec)(nil), // 14: teleport.notifications.v1.UniqueNotificationIdentifierSpec - (*v1.Metadata)(nil), // 15: teleport.header.v1.Metadata - (*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp - (*types.RoleConditions)(nil), // 17: types.RoleConditions + (*ByUsers)(nil), // 7: teleport.notifications.v1.ByUsers + (*UserNotificationState)(nil), // 8: teleport.notifications.v1.UserNotificationState + (*UserNotificationStateSpec)(nil), // 9: teleport.notifications.v1.UserNotificationStateSpec + (*UserNotificationStateStatus)(nil), // 10: teleport.notifications.v1.UserNotificationStateStatus + (*UserLastSeenNotification)(nil), // 11: teleport.notifications.v1.UserLastSeenNotification + (*UserLastSeenNotificationSpec)(nil), // 12: teleport.notifications.v1.UserLastSeenNotificationSpec + (*UserLastSeenNotificationStatus)(nil), // 13: teleport.notifications.v1.UserLastSeenNotificationStatus + (*UniqueNotificationIdentifier)(nil), // 14: teleport.notifications.v1.UniqueNotificationIdentifier + (*UniqueNotificationIdentifierSpec)(nil), // 15: teleport.notifications.v1.UniqueNotificationIdentifierSpec + (*v1.Metadata)(nil), // 16: teleport.header.v1.Metadata + (*timestamppb.Timestamp)(nil), // 17: google.protobuf.Timestamp + (*types.RoleConditions)(nil), // 18: types.RoleConditions } var file_teleport_notifications_v1_notifications_proto_depIdxs = []int32{ - 15, // 0: teleport.notifications.v1.Notification.metadata:type_name -> teleport.header.v1.Metadata + 16, // 0: teleport.notifications.v1.Notification.metadata:type_name -> teleport.header.v1.Metadata 2, // 1: teleport.notifications.v1.Notification.spec:type_name -> teleport.notifications.v1.NotificationSpec - 16, // 2: teleport.notifications.v1.NotificationSpec.created:type_name -> google.protobuf.Timestamp - 15, // 3: teleport.notifications.v1.GlobalNotification.metadata:type_name -> teleport.header.v1.Metadata + 17, // 2: teleport.notifications.v1.NotificationSpec.created:type_name -> google.protobuf.Timestamp + 16, // 3: teleport.notifications.v1.GlobalNotification.metadata:type_name -> teleport.header.v1.Metadata 4, // 4: teleport.notifications.v1.GlobalNotification.spec:type_name -> teleport.notifications.v1.GlobalNotificationSpec 5, // 5: teleport.notifications.v1.GlobalNotificationSpec.by_permissions:type_name -> teleport.notifications.v1.ByPermissions 6, // 6: teleport.notifications.v1.GlobalNotificationSpec.by_roles:type_name -> teleport.notifications.v1.ByRoles - 1, // 7: teleport.notifications.v1.GlobalNotificationSpec.notification:type_name -> teleport.notifications.v1.Notification - 17, // 8: teleport.notifications.v1.ByPermissions.role_conditions:type_name -> types.RoleConditions - 15, // 9: teleport.notifications.v1.UserNotificationState.metadata:type_name -> teleport.header.v1.Metadata - 8, // 10: teleport.notifications.v1.UserNotificationState.spec:type_name -> teleport.notifications.v1.UserNotificationStateSpec - 9, // 11: teleport.notifications.v1.UserNotificationState.status:type_name -> teleport.notifications.v1.UserNotificationStateStatus - 0, // 12: teleport.notifications.v1.UserNotificationStateStatus.notification_state:type_name -> teleport.notifications.v1.NotificationState - 15, // 13: teleport.notifications.v1.UserLastSeenNotification.metadata:type_name -> teleport.header.v1.Metadata - 11, // 14: teleport.notifications.v1.UserLastSeenNotification.spec:type_name -> teleport.notifications.v1.UserLastSeenNotificationSpec - 12, // 15: teleport.notifications.v1.UserLastSeenNotification.status:type_name -> teleport.notifications.v1.UserLastSeenNotificationStatus - 16, // 16: teleport.notifications.v1.UserLastSeenNotificationStatus.last_seen_time:type_name -> google.protobuf.Timestamp - 15, // 17: teleport.notifications.v1.UniqueNotificationIdentifier.metadata:type_name -> teleport.header.v1.Metadata - 14, // 18: teleport.notifications.v1.UniqueNotificationIdentifier.spec:type_name -> teleport.notifications.v1.UniqueNotificationIdentifierSpec - 19, // [19:19] is the sub-list for method output_type - 19, // [19:19] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 7, // 7: teleport.notifications.v1.GlobalNotificationSpec.by_users:type_name -> teleport.notifications.v1.ByUsers + 1, // 8: teleport.notifications.v1.GlobalNotificationSpec.notification:type_name -> teleport.notifications.v1.Notification + 18, // 9: teleport.notifications.v1.ByPermissions.role_conditions:type_name -> types.RoleConditions + 16, // 10: teleport.notifications.v1.UserNotificationState.metadata:type_name -> teleport.header.v1.Metadata + 9, // 11: teleport.notifications.v1.UserNotificationState.spec:type_name -> teleport.notifications.v1.UserNotificationStateSpec + 10, // 12: teleport.notifications.v1.UserNotificationState.status:type_name -> teleport.notifications.v1.UserNotificationStateStatus + 0, // 13: teleport.notifications.v1.UserNotificationStateStatus.notification_state:type_name -> teleport.notifications.v1.NotificationState + 16, // 14: teleport.notifications.v1.UserLastSeenNotification.metadata:type_name -> teleport.header.v1.Metadata + 12, // 15: teleport.notifications.v1.UserLastSeenNotification.spec:type_name -> teleport.notifications.v1.UserLastSeenNotificationSpec + 13, // 16: teleport.notifications.v1.UserLastSeenNotification.status:type_name -> teleport.notifications.v1.UserLastSeenNotificationStatus + 17, // 17: teleport.notifications.v1.UserLastSeenNotificationStatus.last_seen_time:type_name -> google.protobuf.Timestamp + 16, // 18: teleport.notifications.v1.UniqueNotificationIdentifier.metadata:type_name -> teleport.header.v1.Metadata + 15, // 19: teleport.notifications.v1.UniqueNotificationIdentifier.spec:type_name -> teleport.notifications.v1.UniqueNotificationIdentifierSpec + 20, // [20:20] is the sub-list for method output_type + 20, // [20:20] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name } func init() { file_teleport_notifications_v1_notifications_proto_init() } @@ -1306,6 +1376,7 @@ func file_teleport_notifications_v1_notifications_proto_init() { (*GlobalNotificationSpec_ByPermissions)(nil), (*GlobalNotificationSpec_ByRoles)(nil), (*GlobalNotificationSpec_All)(nil), + (*GlobalNotificationSpec_ByUsers)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -1313,7 +1384,7 @@ func file_teleport_notifications_v1_notifications_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_notifications_v1_notifications_proto_rawDesc, NumEnums: 1, - NumMessages: 14, + NumMessages: 15, NumExtensions: 0, NumServices: 0, }, diff --git a/api/proto/teleport/notifications/v1/notifications.proto b/api/proto/teleport/notifications/v1/notifications.proto index 7244ba0da1224..4d71cce500105 100644 --- a/api/proto/teleport/notifications/v1/notifications.proto +++ b/api/proto/teleport/notifications/v1/notifications.proto @@ -80,6 +80,9 @@ message GlobalNotificationSpec { ByRoles by_roles = 2; // all represents whether to target all users, regardless of roles or permissions. bool all = 3; + // by_users represents a list of usernames of the users targeted by this notification. + // If only one user is being targeted, please create a user-specific notification instead. + ByUsers by_users = 7; } // match_all_conditions is whether or not all the conditions specified by the matcher must be met, // if false, only one of the conditions needs to be met. @@ -101,6 +104,11 @@ message ByRoles { repeated string roles = 1; } +// ByUsers represents the users targeted by this notification. +message ByUsers { + repeated string users = 1; +} + // UserNotificationState represents a notification's state for a user. This is to keep track // of whether the user has clicked on or dismissed the notification. message UserNotificationState { diff --git a/api/types/constants.go b/api/types/constants.go index fbc5891caa888..c81f4d3ac084c 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -1166,6 +1166,39 @@ const ( NotificationAccessRequestDeniedSubKind = "access-request-denied" // NotificationAccessRequestPromotedSubKind is the subkind for a notification for a user's access request being promoted to an access list. NotificationAccessRequestPromotedSubKind = "access-request-promoted" + + // NotificationAccessListReviewDue14dSubKind is the subkind for a notification for an access list review due in less than 14 days. + NotificationAccessListReviewDue14dSubKind = "access-list-review-due-14d" + + // NotificationAccessListReviewDue7dSubKind is the subkind for a notification for an access list review due in less than 7 days. + NotificationAccessListReviewDue7dSubKind = "access-list-review-due-7d" + + // NotificationAccessListReviewDue3dSubKind is the subkind for a notification for an access list review due in less than 3 days. + NotificationAccessListReviewDue3dSubKind = "access-list-review-due-3d" + + // NotificationAccessListReviewDue0dSubKind is the subkind for a notification for an access list review due today. + NotificationAccessListReviewDue0dSubKind = "access-list-review-due-0d" + + // NotificationAccessListReviewOverdue3dSubKind is the subkind for a notification for an access list review overdue by 3 days. + NotificationAccessListReviewOverdue3dSubKind = "access-list-review-overdue-3d" + + // NotificationAccessListReviewOverdue7dSubKind is the subkind for a notification for an access list review overdue by 7 days. + NotificationAccessListReviewOverdue7dSubKind = "access-list-review-overdue-7d" +) + +const ( + // NotificationIdentifierPrefixAccessListDueReminder14d is the prefix for unique notification identifiers for 14d access list review reminders. + NotificationIdentifierPrefixAccessListDueReminder14d = "access_list_14d_due_reminder" + // NotificationIdentifierPrefixAccessListDueReminder7d is the prefix for unique notification identifiers for 7d access list review reminders. + NotificationIdentifierPrefixAccessListDueReminder7d = "access_list_7d_due_reminder" + // NotificationIdentifierPrefixAccessListDueReminder3d is the prefix for unique notification identifiers for 3d access list review reminders. + NotificationIdentifierPrefixAccessListDueReminder3d = "access_list_3d_due_reminder" + // NotificationIdentifierPrefixAccessListDueReminder0d is the prefix for unique notification identifiers for 0d (today) access list review reminders. + NotificationIdentifierPrefixAccessListDueReminder0d = "access_list_0d_due_reminder" + // NotificationIdentifierPrefixAccessListDueReminder30d is the prefix for unique notification identifiers for 3d overdue access list review reminders. + NotificationIdentifierPrefixAccessListOverdue3d = "access_list_3d_overdue_reminder" + // NotificationIdentifierPrefixAccessListDueReminder30d is the prefix for unique notification identifiers for 7d overdue access list review reminders. + NotificationIdentifierPrefixAccessListOverdue7d = "access_list_7d_overdue_reminder" ) const ( diff --git a/api/types/semaphore.go b/api/types/semaphore.go index d4e5b26614176..68d32850de075 100644 --- a/api/types/semaphore.go +++ b/api/types/semaphore.go @@ -51,6 +51,10 @@ const SemaphoreKindAccessMonitoringLimiter = "access_monitoring_limiter" // session recordings backend. const SemaphoreKindUploadCompleter = "upload_completer" +// SemaphoreKindAccessListReminderLimiter is the semaphore kind used by +// the periodic check which creates access list reminder notifications. +const SemaphoreKindAccessListReminderLimiter = "access_list_reminder_limiter" + // Semaphore represents distributed semaphore concept type Semaphore interface { // Resource contains common resource values diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 3a124a9fd6412..342257e297fea 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -75,6 +75,7 @@ import ( "github.com/gravitational/teleport/api/internalutils/stream" "github.com/gravitational/teleport/api/metadata" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/accesslist" apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/types/wrappers" apiutils "github.com/gravitational/teleport/api/utils" @@ -166,6 +167,7 @@ const ( const ( notificationsPageReadInterval = 5 * time.Millisecond notificationsWriteInterval = 40 * time.Millisecond + accessListsPageReadInterval = 5 * time.Millisecond ) var ErrRequiresEnterprise = services.ErrRequiresEnterprise @@ -1351,6 +1353,7 @@ const ( desktopCheckKey upgradeWindowCheckKey roleCountKey + accessListReminderNotificationsKey ) // runPeriodicOperations runs some periodic bookkeeping operations @@ -1400,6 +1403,12 @@ func (a *Server) runPeriodicOperations() { FirstDuration: retryutils.FullJitter(time.Minute), Jitter: retryutils.SeventhJitter, }, + interval.SubInterval[periodicIntervalKey]{ + Key: accessListReminderNotificationsKey, + Duration: 8 * time.Hour, + FirstDuration: retryutils.FullJitter(time.Hour), + Jitter: retryutils.SeventhJitter, + }, ) defer ticker.Stop() @@ -1572,6 +1581,8 @@ func (a *Server) runPeriodicOperations() { go a.syncUpgradeWindowStartHour(a.closeCtx) case roleCountKey: go a.tallyRoles(a.closeCtx) + case accessListReminderNotificationsKey: + go a.CreateAccessListReminderNotifications(a.closeCtx) } } } @@ -6124,6 +6135,237 @@ func (a *Server) CleanupNotifications(ctx context.Context) { } } +const ( + accessListReminderSemaphoreName = "access-list-reminder-check" + accessListReminderSemaphoreMaxLeases = 1 +) + +// CreateAccessListReminderNotifications checks if there are any access lists expiring soon and creates notifications to remind their owners if so. +func (a *Server) CreateAccessListReminderNotifications(ctx context.Context) { + // Ensure only one auth server is running this check at a time. + lease, err := services.AcquireSemaphoreLock(ctx, services.SemaphoreLockConfig{ + Service: a, + Clock: a.clock, + Expiry: 5 * time.Minute, + Params: types.AcquireSemaphoreRequest{ + SemaphoreKind: types.SemaphoreKindAccessListReminderLimiter, + SemaphoreName: accessListReminderSemaphoreName, + MaxLeases: accessListReminderSemaphoreMaxLeases, + Holder: a.ServerID, + }, + }) + if err != nil { + a.logger.WarnContext(ctx, "unable to acquire semaphore, will skip this access list reminder check", "server_id", a.ServerID) + return + } + + defer func() { + lease.Stop() + if err := lease.Wait(); err != nil { + a.logger.WarnContext(ctx, "error cleaning up semaphore", "error", err) + } + }() + + now := a.clock.Now() + + // Fetch all access lists + var accessLists []*accesslist.AccessList + var accessListsPageKey string + accessListsReadLimiter := time.NewTicker(accessListsPageReadInterval) + defer accessListsReadLimiter.Stop() + for { + select { + case <-accessListsReadLimiter.C: + case <-ctx.Done(): + return + } + response, nextKey, err := a.Cache.ListAccessLists(ctx, 20, accessListsPageKey) + if err != nil { + a.logger.WarnContext(ctx, "failed to list access lists for periodic reminder notification check", "error", err) + } + + for _, al := range response { + daysDiff := int(al.Spec.Audit.NextAuditDate.Sub(now).Hours() / 24) + // Only keep access lists that fall within our thresholds in memory + if daysDiff <= 15 && daysDiff >= -8 { + accessLists = append(accessLists, al) + } + } + + if nextKey == "" { + break + } + accessListsPageKey = nextKey + } + + reminderThresholds := []struct { + days int + prefix string + notificationSubkind string + }{ + {14, types.NotificationIdentifierPrefixAccessListDueReminder14d, types.NotificationAccessListReviewDue14dSubKind}, + {7, types.NotificationIdentifierPrefixAccessListDueReminder7d, types.NotificationAccessListReviewDue7dSubKind}, + {3, types.NotificationIdentifierPrefixAccessListDueReminder3d, types.NotificationAccessListReviewDue3dSubKind}, + {0, types.NotificationIdentifierPrefixAccessListDueReminder0d, types.NotificationAccessListReviewDue0dSubKind}, + {-3, types.NotificationIdentifierPrefixAccessListOverdue3d, types.NotificationAccessListReviewOverdue3dSubKind}, + {-7, types.NotificationIdentifierPrefixAccessListOverdue7d, types.NotificationAccessListReviewOverdue7dSubKind}, + } + + for _, threshold := range reminderThresholds { + var relevantLists []*accesslist.AccessList + + // Filter access lists based on due date + for _, al := range accessLists { + dueDate := al.Spec.Audit.NextAuditDate + timeDiff := dueDate.Sub(now) + daysDiff := int(timeDiff.Hours() / 24) + + if threshold.days < 0 { + if daysDiff <= threshold.days { + relevantLists = append(relevantLists, al) + } + } else { + if daysDiff >= 0 && daysDiff <= threshold.days { + relevantLists = append(relevantLists, al) + } + } + } + + if len(relevantLists) == 0 { + continue + } + + // Fetch all identifiers for this treshold prefix. + var identifiers []*notificationsv1.UniqueNotificationIdentifier + var nextKey string + for { + identifiersResp, nextKey, err := a.ListUniqueNotificationIdentifiersForPrefix(ctx, threshold.prefix, 0, nextKey) + if err != nil { + a.logger.WarnContext(ctx, "failed to list notification identifiers", "error", err, "prefix", threshold.prefix) + continue + } + identifiers = append(identifiers, identifiersResp...) + if nextKey == "" { + break + } + } + + // Create a map of identifiers for quick lookup + identifiersMap := make(map[string]struct{}) + for _, id := range identifiers { + // id.Spec.UniqueIdentifier is the access list ID + identifiersMap[id.Spec.UniqueIdentifier] = struct{}{} + } + + // owners is the combined list of owners for relevant access lists we are creating the notification for. + var owners []string + + // Check for access lists which haven't already been accounted for in a notification + var needsNotification bool + + writeLimiter := time.NewTicker(notificationsWriteInterval) + for _, accessList := range relevantLists { + select { + case <-writeLimiter.C: + case <-ctx.Done(): + return + } + + if _, exists := identifiersMap[accessList.GetName()]; !exists { + needsNotification = true + // Create a unique identifier for this access list so that we know it has been accounted for. + // Note that if the auth server crashes between creating this identifier and creating the notification, + // the notification will be missed. This has been judged as an acceptable outcome for access lists, + // but the same strategy may not be acceptable for other notification types. + if _, err := a.CreateUniqueNotificationIdentifier(ctx, threshold.prefix, accessList.GetName()); err != nil { + a.logger.WarnContext(ctx, "failed to create notification identifier", "error", err, "access_list", accessList.GetName()) + continue + } + for _, owner := range accessList.Spec.Owners { + owners = append(owners, owner.Name) + } + } + } + writeLimiter.Stop() + + owners = apiutils.Deduplicate(owners) + + var title string + if threshold.days == 0 { + title = "You have access lists due for review today." + } else if threshold.days < 0 { + title = fmt.Sprintf("You have access lists that are more than %d days overdue for review", -threshold.days) + } else { + title = fmt.Sprintf("You have access lists due for review in less than %d days.", threshold.days) + } + + // Create the notification for this reminder treshold for all relevant owners. + if needsNotification { + err := a.createAccessListReminderNotification(ctx, owners, threshold.notificationSubkind, title) + if err != nil { + a.logger.WarnContext(ctx, "Failed to create access list reminder notification", "error", err) + } + } + } +} + +// createAccessListReminderNotification is a helper function to create a notification for an access list reminder. +func (a *Server) createAccessListReminderNotification(ctx context.Context, owners []string, subkind string, title string) error { + _, err := a.Services.CreateGlobalNotification(ctx, ¬ificationsv1.GlobalNotification{ + Spec: ¬ificationsv1.GlobalNotificationSpec{ + Matcher: ¬ificationsv1.GlobalNotificationSpec_ByUsers{ + ByUsers: ¬ificationsv1.ByUsers{ + Users: owners, + }, + }, + Notification: ¬ificationsv1.Notification{ + Spec: ¬ificationsv1.NotificationSpec{}, + SubKind: subkind, + Metadata: &headerv1.Metadata{ + Labels: map[string]string{types.NotificationTitleLabel: title}, + }, + }, + }, + }) + if err != nil { + return err + } + + // Also create a notification for users who have CRUD permissions for access lists. This is because they can also review access lists. + _, err = a.Services.CreateGlobalNotification(ctx, ¬ificationsv1.GlobalNotification{ + Spec: ¬ificationsv1.GlobalNotificationSpec{ + Matcher: ¬ificationsv1.GlobalNotificationSpec_ByPermissions{ + ByPermissions: ¬ificationsv1.ByPermissions{ + RoleConditions: []*types.RoleConditions{ + { + Rules: []types.Rule{ + { + Resources: []string{types.KindAccessList}, + Verbs: services.RW(), + }, + }, + }, + }, + }, + }, + // Exclude the list of owners so that they don't get a duplicate notification, since we already created a notification for them. + ExcludeUsers: owners, + Notification: ¬ificationsv1.Notification{ + Spec: ¬ificationsv1.NotificationSpec{}, + SubKind: subkind, + Metadata: &headerv1.Metadata{ + Labels: map[string]string{types.NotificationTitleLabel: title}, + }, + }, + }, + }) + if err != nil { + return err + } + + return nil +} + // GenerateCertAuthorityCRL generates an empty CRL for the local CA of a given type. func (a *Server) GenerateCertAuthorityCRL(ctx context.Context, caType types.CertAuthType) ([]byte, error) { // Generate a CRL for the current cluster CA. diff --git a/lib/auth/auth_test.go b/lib/auth/auth_test.go index 485490b7be37f..0bebbb4fc12cb 100644 --- a/lib/auth/auth_test.go +++ b/lib/auth/auth_test.go @@ -56,6 +56,7 @@ import ( mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1" notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/accesslist" apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/types/header" "github.com/gravitational/teleport/api/types/installers" @@ -64,6 +65,7 @@ import ( "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/api/utils/keys" "github.com/gravitational/teleport/api/utils/sshutils" + "github.com/gravitational/teleport/entitlements" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/auth/keystore" "github.com/gravitational/teleport/lib/auth/testauthority" @@ -4341,6 +4343,109 @@ func TestCleanupNotifications(t *testing.T) { }, 3*time.Second, 100*time.Millisecond) } +func TestCreateAccessListReminderNotifications(t *testing.T) { + ctx := context.Background() + + modules.SetTestModules(t, &modules.TestModules{ + TestBuildType: modules.BuildEnterprise, + TestFeatures: modules.Features{ + Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{ + entitlements.Identity: {Enabled: true}, + }, + }, + }) + + // Setup test auth server + testServer := newTestTLSServer(t) + authServer := testServer.Auth() + + testRole, err := types.NewRole("test", types.RoleSpecV6{ + Allow: types.RoleConditions{ + Logins: []string{"user"}, + ReviewRequests: &types.AccessReviewConditions{}, + }, + }) + require.NoError(t, err) + _, err = authServer.UpsertRole(ctx, testRole) + require.NoError(t, err) + + testUsername := "user1" + user, err := types.NewUser(testUsername) + require.NoError(t, err) + user.SetRoles([]string{"test"}) + _, err = authServer.UpsertUser(ctx, user) + require.NoError(t, err) + + client, err := testServer.NewClient(TestUser(testUsername)) + require.NoError(t, err) + + // Helper to create access lists with specific next audit dates + createAccessList := func(t *testing.T, name string, nextAuditDate time.Time) { + al, err := accesslist.NewAccessList(header.Metadata{ + Name: name, + }, accesslist.Spec{ + Title: fmt.Sprintf("Access List %s", name), + Description: fmt.Sprintf("Test access list %s", name), + Owners: []accesslist.Owner{{ + Name: testUsername, + Description: "", + MembershipKind: "", + }}, + Audit: accesslist.Audit{ + NextAuditDate: nextAuditDate, + Recurrence: accesslist.Recurrence{}, + Notifications: accesslist.Notifications{}, + }, + Grants: accesslist.Grants{ + Roles: []string{"grant"}, + }, + }) + require.NoError(t, err) + + _, err = authServer.UpsertAccessList(ctx, al) + require.NoError(t, err) + } + + // Create access lists with different expiry times + accessLists := []struct { + name string + dueInDays int + }{ + {name: "al-due-13d", dueInDays: 13}, + {name: "al-due-12d", dueInDays: 12}, + {name: "al-due-5d", dueInDays: 5}, + {name: "al-due-2d", dueInDays: 2}, + {name: "al-overdue-4d", dueInDays: -4}, + {name: "al-overdue-8d", dueInDays: -8}, + {name: "al-due-60d", dueInDays: 60}, // there should be no notification for this one + {name: "al-overdue-today", dueInDays: 0}, + } + + for _, al := range accessLists { + createAccessList(t, al.name, authServer.clock.Now().Add(time.Duration(al.dueInDays)*24*time.Hour)) + } + + // Run CreateAccessListReminderNotifications() + authServer.CreateAccessListReminderNotifications(ctx) + + // Check notifications + resp, err := client.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{ + PageSize: 50, + }) + require.NoError(t, err) + require.Len(t, resp.Notifications, 6) + + // Run CreateAccessListReminderNotifications() again to verify no duplicates are created if it's run again. + authServer.CreateAccessListReminderNotifications(ctx) + + // Check notifications again, counts should remain the same. + resp, err = client.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{ + PageSize: 50, + }) + require.NoError(t, err) + require.Len(t, resp.Notifications, 6) +} + func TestServer_GetAnonymizationKey(t *testing.T) { tests := []struct { name string diff --git a/lib/auth/notification_test.go b/lib/auth/notification_test.go index 9bf65157aa99e..4e429761194aa 100644 --- a/lib/auth/notification_test.go +++ b/lib/auth/notification_test.go @@ -265,6 +265,16 @@ func TestNotifications(t *testing.T) { }, }, }, + { + // Matcher matches by usernames. + globalNotification: newGlobalNotification(t, "auditor-9,manager-9", ¬ificationsv1.GlobalNotificationSpec{ + Matcher: ¬ificationsv1.GlobalNotificationSpec_ByUsers{ + ByUsers: ¬ificationsv1.ByUsers{ + Users: []string{auditorUsername, managerUsername}, + }, + }, + }), + }, } notificationIdMap := map[string]string{} @@ -292,7 +302,7 @@ func TestNotifications(t *testing.T) { require.NoError(t, err) defer auditorClient.Close() - auditorExpectedNotifications := []string{"auditor-8,manager-6", "auditor-7", "auditor-6", "auditor-5,manager-2", "auditor-4", "auditor-3", "auditor-2", "auditor-1"} + auditorExpectedNotifications := []string{"auditor-9,manager-9", "auditor-8,manager-6", "auditor-7", "auditor-6", "auditor-5,manager-2", "auditor-4", "auditor-3", "auditor-2", "auditor-1"} var finalOut []*notificationsv1.Notification @@ -315,19 +325,19 @@ func TestNotifications(t *testing.T) { // Verify that the nextKeys are correct. expectedUserNotifsNextKey := notificationIdMap["auditor-4"] - expectedGlobalNotifsNextKey := notificationIdMap["auditor-5,manager-2"] + expectedGlobalNotifsNextKey := notificationIdMap["auditor-6"] expectedNextKeys := fmt.Sprintf("%s,%s", expectedUserNotifsNextKey, - expectedGlobalNotifsNextKey) // "," + expectedGlobalNotifsNextKey) // "," require.Equal(t, expectedNextKeys, resp.NextPageToken) require.Equal(t, lastSeenTimestamp.GetSeconds(), resp.UserLastSeenNotificationTimestamp.GetSeconds()) - // Fetch the next 3 notifications, starting from the previously received startKeys. + // Fetch the next 4 notifications, starting from the previously received startKeys. // After this fetch, there should be no more global notifications for auditor, so the next page token // for global notifications should be "". resp, err = auditorClient.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{ - PageSize: 3, + PageSize: 4, PageToken: resp.NextPageToken, }) require.NoError(t, err) @@ -382,7 +392,7 @@ func TestNotifications(t *testing.T) { resp, err = auditorClient.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{ PageSize: 10, }) - auditorExpectedNotifsAfterDismissal := []string{"auditor-8,manager-6", "auditor-7", "auditor-6", "auditor-4", "auditor-3", "auditor-1"} + auditorExpectedNotifsAfterDismissal := []string{"auditor-9,manager-9", "auditor-8,manager-6", "auditor-7", "auditor-6", "auditor-4", "auditor-3", "auditor-1"} require.NoError(t, err) require.Equal(t, auditorExpectedNotifsAfterDismissal, notificationsToTitlesList(t, resp.Notifications)) @@ -391,7 +401,7 @@ func TestNotifications(t *testing.T) { require.NoError(t, err) defer managerClient.Close() - managerExpectedNotifications := []string{"manager-8-expires", "manager-7-expires", "auditor-8,manager-6", "manager-5", "manager-4", "manager-3", "auditor-5,manager-2", "manager-1"} + managerExpectedNotifications := []string{"auditor-9,manager-9", "manager-8-expires", "manager-7-expires", "auditor-8,manager-6", "manager-5", "manager-4", "manager-3", "auditor-5,manager-2", "manager-1"} resp, err = managerClient.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{ PageSize: 10, @@ -419,7 +429,7 @@ func TestNotifications(t *testing.T) { }) require.NoError(t, err) - clickedNotification := resp.Notifications[0] // "manager-8-expires" is the first item in the list + clickedNotification := resp.Notifications[1] // "manager-8-expires" is the second item in the list clickedLabelValue := clickedNotification.GetMetadata().GetLabels()[types.NotificationClickedLabel] require.Equal(t, "true", clickedLabelValue) @@ -429,7 +439,7 @@ func TestNotifications(t *testing.T) { // Verify that notification "manager-8-expires" is now no longer returned. resp, err = managerClient.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{}) require.NoError(t, err) - require.Equal(t, managerExpectedNotifications[1:], notificationsToTitlesList(t, resp.Notifications)) + require.NotContains(t, notificationsToTitlesList(t, resp.Notifications), "manager-8-expires") // Advance 16 minutes. fakeClock.Advance(16 * time.Minute) @@ -437,7 +447,7 @@ func TestNotifications(t *testing.T) { // Verify that notification "manager-7-expires" is now no longer returned either. resp, err = managerClient.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{}) require.NoError(t, err) - require.Equal(t, managerExpectedNotifications[2:], notificationsToTitlesList(t, resp.Notifications)) + require.NotContains(t, notificationsToTitlesList(t, resp.Notifications), "manager-7-expires") // Verify that manager can't upsert a notification state for auditor _, err = managerClient.UpsertUserNotificationState(ctx, auditorUsername, ¬ificationsv1.UserNotificationState{ diff --git a/lib/auth/notifications/notificationsv1/service.go b/lib/auth/notifications/notificationsv1/service.go index 8fb30cff72410..15bf0ba559cc5 100644 --- a/lib/auth/notifications/notificationsv1/service.go +++ b/lib/auth/notifications/notificationsv1/service.go @@ -307,6 +307,10 @@ func (s *Service) matchGlobalNotification(ctx context.Context, authCtx *authz.Co // Always return true if the matcher is "all." return true + case *notificationsv1.GlobalNotificationSpec_ByUsers: + userList := matcher.ByUsers.GetUsers() + return slices.Contains(userList, authCtx.User.GetName()) + case *notificationsv1.GlobalNotificationSpec_ByRoles: matcherRoles := matcher.ByRoles.GetRoles() userRoles := authCtx.User.GetRoles() diff --git a/web/packages/teleport/src/Notifications/Notifications.tsx b/web/packages/teleport/src/Notifications/Notifications.tsx index f81df41610cd6..a82b338ba2365 100644 --- a/web/packages/teleport/src/Notifications/Notifications.tsx +++ b/web/packages/teleport/src/Notifications/Notifications.tsx @@ -346,6 +346,7 @@ function EmptyState() { ); } +//TODO(rudream): Delete local notifications /** accessListStoreNotificationsToNotifications converts a list of access list notifications from the notifications store into the primary * Notification type used by the notifications list. */ diff --git a/web/packages/teleport/src/Notifications/fixtures.ts b/web/packages/teleport/src/Notifications/fixtures.ts index 798230906a402..94fb0e8d65515 100644 --- a/web/packages/teleport/src/Notifications/fixtures.ts +++ b/web/packages/teleport/src/Notifications/fixtures.ts @@ -115,4 +115,52 @@ export const notifications: Notification[] = [ }, ], }, + { + id: '8', + title: `You have access lists that require your review today.`, + subKind: NotificationSubKind.NotificationAccessListReviewDue0d, + createdDate: subMinutes(Date.now(), 5), // 5 minutes ago + clicked: false, + labels: [], + }, + { + id: '9', + title: `You have access lists that require your review within 14 days.`, + subKind: NotificationSubKind.NotificationAccessListReviewDue14d, + createdDate: subMinutes(Date.now(), 6), // 6 minutes ago + clicked: false, + labels: [], + }, + { + id: '10', + title: `You have access lists that require your review within 7 days.`, + subKind: NotificationSubKind.NotificationAccessListReviewDue7d, + createdDate: subMinutes(Date.now(), 7), // 7 minutes ago + clicked: false, + labels: [], + }, + { + id: '11', + title: `You have access lists that require your review within 3 days.`, + subKind: NotificationSubKind.NotificationAccessListReviewDue3d, + createdDate: subMinutes(Date.now(), 8), // 8 minutes ago + clicked: false, + labels: [], + }, + { + id: '12', + title: `You have access lists overdue for review by more than 3 days.`, + subKind: NotificationSubKind.NotificationAccessListReviewOverdue3d, + createdDate: subMinutes(Date.now(), 9), // 9 minutes ago + clicked: false, + labels: [], + }, + { + id: '13', + title: `You have access lists overdue for review by more than 7 days.`, + subKind: NotificationSubKind.NotificationAccessListReviewOverdue7d, + createdDate: subMinutes(Date.now(), 10), // 10 minutes ago + clicked: false, + labels: [], + }, ]; diff --git a/web/packages/teleport/src/services/notifications/types.ts b/web/packages/teleport/src/services/notifications/types.ts index 328288b3732db..01286e6b93bab 100644 --- a/web/packages/teleport/src/services/notifications/types.ts +++ b/web/packages/teleport/src/services/notifications/types.ts @@ -92,20 +92,31 @@ export type Notification = { export enum NotificationSubKind { DefaultInformational = 'default-informational', DefaultWarning = 'default-warning', + UserCreatedInformational = 'user-created-informational', UserCreatedWarning = 'user-created-warning', + AccessRequestPending = 'access-request-pending', AccessRequestApproved = 'access-request-approved', AccessRequestDenied = 'access-request-denied', AccessRequestPromoted = 'access-request-promoted', + + NotificationAccessListReviewDue14d = 'access-list-review-due-14d', + NotificationAccessListReviewDue7d = 'access-list-review-due-7d', + NotificationAccessListReviewDue3d = 'access-list-review-due-3d', + NotificationAccessListReviewDue0d = 'access-list-review-due-0d', + NotificationAccessListReviewOverdue3d = 'access-list-review-overdue-3d', + NotificationAccessListReviewOverdue7d = 'access-list-review-overdue-7d', } +//TODO(rudream): Delete local notifications /** LocalNotificationKind is the kind of local notifications which are generated on the frontend and not stored in the backend. These do not need to be kept in sync with the backend. */ export enum LocalNotificationKind { /** AccessList is a notification for an access list reminder. */ AccessList = 'access-list', } +//TODO(rudream): Delete local notifications /** LocalNotificationGroupedKind is the kind of groupings of local notifications. These do not need to be kept in sync with the backend. */ export enum LocalNotificationGroupedKind { /** AccessListGrouping is a notification which combines the notifications for multiple access list reminders into one to reduce clutter. */