From 6962f77dd36cbc965947ec6b89965326f6406e18 Mon Sep 17 00:00:00 2001
From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com>
Date: Thu, 15 Aug 2024 12:36:09 -0400
Subject: [PATCH] Add tctl notifications commands (#42124) (#45503)
---
api/client/client.go | 24 +
.../v1/notifications_service.pb.go | 447 +++++++++++-------
.../v1/notifications_service.proto | 12 +
api/types/constants.go | 2 +
lib/auth/authclient/clt.go | 34 +-
.../notifications/notificationsv1/service.go | 200 ++++++++
lib/services/presets.go | 2 +
lib/web/ui/notification.go | 26 +-
tool/tctl/common/cmds.go | 1 +
tool/tctl/common/helpers_test.go | 10 +
tool/tctl/common/notification_command.go | 313 ++++++++++++
tool/tctl/common/notification_command_test.go | 113 +++++
.../src/Notifications/Notification.tsx | 22 +
.../notificationContentFactory.tsx | 7 +-
.../services/notifications/notifications.ts | 4 +-
.../src/services/notifications/types.ts | 4 +
16 files changed, 1019 insertions(+), 202 deletions(-)
create mode 100644 tool/tctl/common/notification_command.go
create mode 100644 tool/tctl/common/notification_command_test.go
diff --git a/api/client/client.go b/api/client/client.go
index 22f8e31005413..61aa64698b2fd 100644
--- a/api/client/client.go
+++ b/api/client/client.go
@@ -4863,6 +4863,30 @@ func (c *Client) ListNotifications(ctx context.Context, req *notificationsv1pb.L
return rsp, trace.Wrap(err)
}
+// CreateGlobalNotification creates a global notification.
+func (c *Client) CreateGlobalNotification(ctx context.Context, req *notificationsv1pb.CreateGlobalNotificationRequest) (*notificationsv1pb.GlobalNotification, error) {
+ rsp, err := c.NotificationServiceClient().CreateGlobalNotification(ctx, req)
+ return rsp, trace.Wrap(err)
+}
+
+// CreateUserNotification creates a user-specific notification.
+func (c *Client) CreateUserNotification(ctx context.Context, req *notificationsv1pb.CreateUserNotificationRequest) (*notificationsv1pb.Notification, error) {
+ rsp, err := c.NotificationServiceClient().CreateUserNotification(ctx, req)
+ return rsp, trace.Wrap(err)
+}
+
+// DeleteGlobalNotification deletes a global notification.
+func (c *Client) DeleteGlobalNotification(ctx context.Context, req *notificationsv1pb.DeleteGlobalNotificationRequest) error {
+ _, err := c.NotificationServiceClient().DeleteGlobalNotification(ctx, req)
+ return trace.Wrap(err)
+}
+
+// DeleteUserNotification not implemented: can only be called locally.
+func (c *Client) DeleteUserNotification(ctx context.Context, req *notificationsv1pb.DeleteUserNotificationRequest) error {
+ _, err := c.NotificationServiceClient().DeleteUserNotification(ctx, req)
+ return trace.Wrap(err)
+}
+
// UpsertUserNotificationState creates or updates a user notification state which records whether the user has clicked on or dismissed a notification.
func (c *Client) UpsertUserNotificationState(ctx context.Context, req *notificationsv1pb.UpsertUserNotificationStateRequest) (*notificationsv1pb.UserNotificationState, error) {
rsp, err := c.NotificationServiceClient().UpsertUserNotificationState(ctx, req)
diff --git a/api/gen/proto/go/teleport/notifications/v1/notifications_service.pb.go b/api/gen/proto/go/teleport/notifications/v1/notifications_service.pb.go
index af5b92c2b3d7c..df5715ffb5fe4 100644
--- a/api/gen/proto/go/teleport/notifications/v1/notifications_service.pb.go
+++ b/api/gen/proto/go/teleport/notifications/v1/notifications_service.pb.go
@@ -165,6 +165,8 @@ type ListNotificationsRequest struct {
PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
// page_token is the next_page_token value returned from a previous ListUserNotifications request, if any.
PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
+ // filters specify search criteria to limit which notifications should be returned. If omitted, the default behavior will be to list all notifications.
+ Filters *NotificationFilters `protobuf:"bytes,3,opt,name=filters,proto3" json:"filters,omitempty"`
}
func (x *ListNotificationsRequest) Reset() {
@@ -213,6 +215,80 @@ func (x *ListNotificationsRequest) GetPageToken() string {
return ""
}
+func (x *ListNotificationsRequest) GetFilters() *NotificationFilters {
+ if x != nil {
+ return x.Filters
+ }
+ return nil
+}
+
+// NotificationFilters provide a mechanism to refine ListNotification results.
+type NotificationFilters struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // username is the username of the user the notifications being listed are for.
+ Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
+ // global_only is whether to only list global notifications (notifications capable of targetting multiple users).
+ GlobalOnly bool `protobuf:"varint,2,opt,name=global_only,json=globalOnly,proto3" json:"global_only,omitempty"`
+ // user_created_only is whether to only list user-created notifications (ie. notifications created by an admin via the tctl interface).
+ UserCreatedOnly bool `protobuf:"varint,3,opt,name=user_created_only,json=userCreatedOnly,proto3" json:"user_created_only,omitempty"`
+}
+
+func (x *NotificationFilters) Reset() {
+ *x = NotificationFilters{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *NotificationFilters) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NotificationFilters) ProtoMessage() {}
+
+func (x *NotificationFilters) ProtoReflect() protoreflect.Message {
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use NotificationFilters.ProtoReflect.Descriptor instead.
+func (*NotificationFilters) Descriptor() ([]byte, []int) {
+ return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *NotificationFilters) GetUsername() string {
+ if x != nil {
+ return x.Username
+ }
+ return ""
+}
+
+func (x *NotificationFilters) GetGlobalOnly() bool {
+ if x != nil {
+ return x.GlobalOnly
+ }
+ return false
+}
+
+func (x *NotificationFilters) GetUserCreatedOnly() bool {
+ if x != nil {
+ return x.UserCreatedOnly
+ }
+ return false
+}
+
// ListNotificationsResponse is the response from listing a user's notifications.
type ListNotificationsResponse struct {
state protoimpl.MessageState
@@ -230,7 +306,7 @@ type ListNotificationsResponse struct {
func (x *ListNotificationsResponse) Reset() {
*x = ListNotificationsResponse{}
if protoimpl.UnsafeEnabled {
- mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[3]
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -243,7 +319,7 @@ func (x *ListNotificationsResponse) String() string {
func (*ListNotificationsResponse) ProtoMessage() {}
func (x *ListNotificationsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[3]
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -256,7 +332,7 @@ func (x *ListNotificationsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListNotificationsResponse.ProtoReflect.Descriptor instead.
func (*ListNotificationsResponse) Descriptor() ([]byte, []int) {
- return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{3}
+ return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{4}
}
func (x *ListNotificationsResponse) GetNotifications() []*Notification {
@@ -293,7 +369,7 @@ type CreateGlobalNotificationRequest struct {
func (x *CreateGlobalNotificationRequest) Reset() {
*x = CreateGlobalNotificationRequest{}
if protoimpl.UnsafeEnabled {
- mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[4]
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -306,7 +382,7 @@ func (x *CreateGlobalNotificationRequest) String() string {
func (*CreateGlobalNotificationRequest) ProtoMessage() {}
func (x *CreateGlobalNotificationRequest) ProtoReflect() protoreflect.Message {
- mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[4]
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -319,7 +395,7 @@ func (x *CreateGlobalNotificationRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateGlobalNotificationRequest.ProtoReflect.Descriptor instead.
func (*CreateGlobalNotificationRequest) Descriptor() ([]byte, []int) {
- return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{4}
+ return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{5}
}
func (x *CreateGlobalNotificationRequest) GetGlobalNotification() *GlobalNotification {
@@ -342,7 +418,7 @@ type DeleteGlobalNotificationRequest struct {
func (x *DeleteGlobalNotificationRequest) Reset() {
*x = DeleteGlobalNotificationRequest{}
if protoimpl.UnsafeEnabled {
- mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[5]
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -355,7 +431,7 @@ func (x *DeleteGlobalNotificationRequest) String() string {
func (*DeleteGlobalNotificationRequest) ProtoMessage() {}
func (x *DeleteGlobalNotificationRequest) ProtoReflect() protoreflect.Message {
- mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[5]
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -368,7 +444,7 @@ func (x *DeleteGlobalNotificationRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeleteGlobalNotificationRequest.ProtoReflect.Descriptor instead.
func (*DeleteGlobalNotificationRequest) Descriptor() ([]byte, []int) {
- return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{5}
+ return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{6}
}
func (x *DeleteGlobalNotificationRequest) GetNotificationId() string {
@@ -393,7 +469,7 @@ type UpsertUserNotificationStateRequest struct {
func (x *UpsertUserNotificationStateRequest) Reset() {
*x = UpsertUserNotificationStateRequest{}
if protoimpl.UnsafeEnabled {
- mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[6]
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -406,7 +482,7 @@ func (x *UpsertUserNotificationStateRequest) String() string {
func (*UpsertUserNotificationStateRequest) ProtoMessage() {}
func (x *UpsertUserNotificationStateRequest) ProtoReflect() protoreflect.Message {
- mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[6]
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -419,7 +495,7 @@ func (x *UpsertUserNotificationStateRequest) ProtoReflect() protoreflect.Message
// Deprecated: Use UpsertUserNotificationStateRequest.ProtoReflect.Descriptor instead.
func (*UpsertUserNotificationStateRequest) Descriptor() ([]byte, []int) {
- return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{6}
+ return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{7}
}
func (x *UpsertUserNotificationStateRequest) GetUsername() string {
@@ -451,7 +527,7 @@ type UpsertUserLastSeenNotificationRequest struct {
func (x *UpsertUserLastSeenNotificationRequest) Reset() {
*x = UpsertUserLastSeenNotificationRequest{}
if protoimpl.UnsafeEnabled {
- mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[7]
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -464,7 +540,7 @@ func (x *UpsertUserLastSeenNotificationRequest) String() string {
func (*UpsertUserLastSeenNotificationRequest) ProtoMessage() {}
func (x *UpsertUserLastSeenNotificationRequest) ProtoReflect() protoreflect.Message {
- mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[7]
+ mi := &file_teleport_notifications_v1_notifications_service_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -477,7 +553,7 @@ func (x *UpsertUserLastSeenNotificationRequest) ProtoReflect() protoreflect.Mess
// Deprecated: Use UpsertUserLastSeenNotificationRequest.ProtoReflect.Descriptor instead.
func (*UpsertUserLastSeenNotificationRequest) Descriptor() ([]byte, []int) {
- return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{7}
+ return file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP(), []int{8}
}
func (x *UpsertUserLastSeenNotificationRequest) GetUsername() string {
@@ -524,128 +600,141 @@ var file_teleport_notifications_v1_notifications_service_proto_rawDesc = []byte{
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x69, 0x66,
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64,
- 0x22, 0x56, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09,
- 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
- 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67,
- 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70,
- 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x80, 0x02, 0x0a, 0x19, 0x4c, 0x69, 0x73,
- 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65,
- 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69,
- 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 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, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61,
- 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
- 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x6c, 0x0a,
- 0x25, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f,
- 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d,
- 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 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, 0x21, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x61,
- 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
- 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x81, 0x01, 0x0a, 0x1f,
- 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69,
- 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
- 0x5e, 0x0a, 0x13, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69,
- 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e,
- 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x67, 0x6c, 0x6f,
- 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22,
- 0x4a, 0x0a, 0x1f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e,
- 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
- 0x73, 0x74, 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, 0x22, 0xaa, 0x01, 0x0a, 0x22,
- 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69,
- 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
- 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
- 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x68,
- 0x0a, 0x17, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
- 0x30, 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, 0x52, 0x15, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0xb7, 0x01, 0x0a, 0x25, 0x55, 0x70, 0x73,
- 0x65, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e,
- 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
- 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
- 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x72,
- 0x0a, 0x1b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e,
- 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
- 0x01, 0x28, 0x0b, 0x32, 0x33, 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, 0x52, 0x18, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x61,
- 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
- 0x6f, 0x6e, 0x32, 0xa1, 0x07, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7b, 0x0a, 0x16, 0x43, 0x72,
- 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66,
- 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27,
+ 0x22, 0xa0, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a,
+ 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
+ 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61,
+ 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
+ 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x48, 0x0a, 0x07, 0x66, 0x69, 0x6c,
+ 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 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, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x74,
+ 0x65, 0x72, 0x73, 0x22, 0x7e, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73,
+ 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73,
+ 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+ 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x67, 0x6c, 0x6f,
+ 0x62, 0x61, 0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x5f,
+ 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01,
+ 0x28, 0x08, 0x52, 0x0f, 0x75, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x4f,
+ 0x6e, 0x6c, 0x79, 0x22, 0x80, 0x02, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x12, 0x4d, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 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, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+ 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f,
+ 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50,
+ 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x6c, 0x0a, 0x25, 0x75, 0x73, 0x65, 0x72,
+ 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66,
+ 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
+ 0x70, 0x18, 0x04, 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, 0x21, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65,
+ 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d,
+ 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x81, 0x01, 0x0a, 0x1f, 0x43, 0x72, 0x65, 0x61, 0x74,
+ 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5e, 0x0a, 0x13, 0x67, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f,
+ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x4a, 0x0a, 0x1f, 0x44, 0x65,
+ 0x6c, 0x65, 0x74, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x22, 0xaa, 0x01, 0x0a, 0x22, 0x55, 0x70, 0x73, 0x65, 0x72,
+ 0x74, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
+ 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x68, 0x0a, 0x17, 0x75, 0x73, 0x65,
+ 0x72, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73,
+ 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 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, 0x52, 0x15, 0x75, 0x73,
+ 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74,
+ 0x61, 0x74, 0x65, 0x22, 0xb7, 0x01, 0x0a, 0x25, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x55, 0x73,
+ 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
+ 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x72, 0x0a, 0x1b, 0x75, 0x73, 0x65,
+ 0x72, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x6e, 0x6f, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33,
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, 0x12, 0x6a, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74,
- 0x65, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
- 0x6e, 0x12, 0x38, 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, 0x44, 0x65,
- 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
- 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
- 0x70, 0x74, 0x79, 0x12, 0x85, 0x01, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x6c,
- 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
- 0x12, 0x3a, 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, 0x43, 0x72, 0x65,
- 0x61, 0x74, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63,
- 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x74,
+ 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, 0x52, 0x18, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65,
+ 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0xa1, 0x07,
+ 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65,
+ 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7b, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55,
+ 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,
+ 0x38, 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, 0x43, 0x72, 0x65, 0x61,
+ 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x12, 0x6a, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,
+ 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 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, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e,
- 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6e, 0x0a, 0x18, 0x44,
- 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66,
- 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f,
+ 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55,
+ 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x85,
+ 0x01, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e,
+ 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 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, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
- 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,
- 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
- 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x7e, 0x0a, 0x11, 0x4c,
- 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
- 0x12, 0x33, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69,
+ 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6e, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
+ 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x12, 0x3a, 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, 0x44,
+ 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66,
+ 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
+ 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+ 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x7e, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f,
+ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x33, 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, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+ 0x1a, 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, 0x4c, 0x69, 0x73,
0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65,
- 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74,
+ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8e, 0x01, 0x0a, 0x1b, 0x55, 0x70, 0x73, 0x65, 0x72,
+ 0x74, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3d, 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, 0x70, 0x73, 0x65, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 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, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
- 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8e, 0x01, 0x0a, 0x1b,
- 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69,
- 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3d, 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, 0x70, 0x73, 0x65, 0x72, 0x74, 0x55, 0x73,
- 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74,
- 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 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, 0x12, 0x97, 0x01, 0x0a,
- 0x1e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 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,
- 0x40, 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, 0x70, 0x73, 0x65,
+ 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x97, 0x01, 0x0a, 0x1e, 0x55, 0x70, 0x73, 0x65,
0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f,
- 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
- 0x74, 0x1a, 0x33, 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, 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, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 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, 0x70, 0x73, 0x65, 0x72, 0x74, 0x55, 0x73, 0x65,
+ 0x72, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 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, 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 (
@@ -660,49 +749,51 @@ func file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP() []
return file_teleport_notifications_v1_notifications_service_proto_rawDescData
}
-var file_teleport_notifications_v1_notifications_service_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
+var file_teleport_notifications_v1_notifications_service_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_teleport_notifications_v1_notifications_service_proto_goTypes = []interface{}{
(*CreateUserNotificationRequest)(nil), // 0: teleport.notifications.v1.CreateUserNotificationRequest
(*DeleteUserNotificationRequest)(nil), // 1: teleport.notifications.v1.DeleteUserNotificationRequest
(*ListNotificationsRequest)(nil), // 2: teleport.notifications.v1.ListNotificationsRequest
- (*ListNotificationsResponse)(nil), // 3: teleport.notifications.v1.ListNotificationsResponse
- (*CreateGlobalNotificationRequest)(nil), // 4: teleport.notifications.v1.CreateGlobalNotificationRequest
- (*DeleteGlobalNotificationRequest)(nil), // 5: teleport.notifications.v1.DeleteGlobalNotificationRequest
- (*UpsertUserNotificationStateRequest)(nil), // 6: teleport.notifications.v1.UpsertUserNotificationStateRequest
- (*UpsertUserLastSeenNotificationRequest)(nil), // 7: teleport.notifications.v1.UpsertUserLastSeenNotificationRequest
- (*Notification)(nil), // 8: teleport.notifications.v1.Notification
- (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp
- (*GlobalNotification)(nil), // 10: teleport.notifications.v1.GlobalNotification
- (*UserNotificationState)(nil), // 11: teleport.notifications.v1.UserNotificationState
- (*UserLastSeenNotification)(nil), // 12: teleport.notifications.v1.UserLastSeenNotification
- (*emptypb.Empty)(nil), // 13: google.protobuf.Empty
+ (*NotificationFilters)(nil), // 3: teleport.notifications.v1.NotificationFilters
+ (*ListNotificationsResponse)(nil), // 4: teleport.notifications.v1.ListNotificationsResponse
+ (*CreateGlobalNotificationRequest)(nil), // 5: teleport.notifications.v1.CreateGlobalNotificationRequest
+ (*DeleteGlobalNotificationRequest)(nil), // 6: teleport.notifications.v1.DeleteGlobalNotificationRequest
+ (*UpsertUserNotificationStateRequest)(nil), // 7: teleport.notifications.v1.UpsertUserNotificationStateRequest
+ (*UpsertUserLastSeenNotificationRequest)(nil), // 8: teleport.notifications.v1.UpsertUserLastSeenNotificationRequest
+ (*Notification)(nil), // 9: teleport.notifications.v1.Notification
+ (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
+ (*GlobalNotification)(nil), // 11: teleport.notifications.v1.GlobalNotification
+ (*UserNotificationState)(nil), // 12: teleport.notifications.v1.UserNotificationState
+ (*UserLastSeenNotification)(nil), // 13: teleport.notifications.v1.UserLastSeenNotification
+ (*emptypb.Empty)(nil), // 14: google.protobuf.Empty
}
var file_teleport_notifications_v1_notifications_service_proto_depIdxs = []int32{
- 8, // 0: teleport.notifications.v1.CreateUserNotificationRequest.notification:type_name -> teleport.notifications.v1.Notification
- 8, // 1: teleport.notifications.v1.ListNotificationsResponse.notifications:type_name -> teleport.notifications.v1.Notification
- 9, // 2: teleport.notifications.v1.ListNotificationsResponse.user_last_seen_notification_timestamp:type_name -> google.protobuf.Timestamp
- 10, // 3: teleport.notifications.v1.CreateGlobalNotificationRequest.global_notification:type_name -> teleport.notifications.v1.GlobalNotification
- 11, // 4: teleport.notifications.v1.UpsertUserNotificationStateRequest.user_notification_state:type_name -> teleport.notifications.v1.UserNotificationState
- 12, // 5: teleport.notifications.v1.UpsertUserLastSeenNotificationRequest.user_last_seen_notification:type_name -> teleport.notifications.v1.UserLastSeenNotification
- 0, // 6: teleport.notifications.v1.NotificationService.CreateUserNotification:input_type -> teleport.notifications.v1.CreateUserNotificationRequest
- 1, // 7: teleport.notifications.v1.NotificationService.DeleteUserNotification:input_type -> teleport.notifications.v1.DeleteUserNotificationRequest
- 4, // 8: teleport.notifications.v1.NotificationService.CreateGlobalNotification:input_type -> teleport.notifications.v1.CreateGlobalNotificationRequest
- 5, // 9: teleport.notifications.v1.NotificationService.DeleteGlobalNotification:input_type -> teleport.notifications.v1.DeleteGlobalNotificationRequest
- 2, // 10: teleport.notifications.v1.NotificationService.ListNotifications:input_type -> teleport.notifications.v1.ListNotificationsRequest
- 6, // 11: teleport.notifications.v1.NotificationService.UpsertUserNotificationState:input_type -> teleport.notifications.v1.UpsertUserNotificationStateRequest
- 7, // 12: teleport.notifications.v1.NotificationService.UpsertUserLastSeenNotification:input_type -> teleport.notifications.v1.UpsertUserLastSeenNotificationRequest
- 8, // 13: teleport.notifications.v1.NotificationService.CreateUserNotification:output_type -> teleport.notifications.v1.Notification
- 13, // 14: teleport.notifications.v1.NotificationService.DeleteUserNotification:output_type -> google.protobuf.Empty
- 10, // 15: teleport.notifications.v1.NotificationService.CreateGlobalNotification:output_type -> teleport.notifications.v1.GlobalNotification
- 13, // 16: teleport.notifications.v1.NotificationService.DeleteGlobalNotification:output_type -> google.protobuf.Empty
- 3, // 17: teleport.notifications.v1.NotificationService.ListNotifications:output_type -> teleport.notifications.v1.ListNotificationsResponse
- 11, // 18: teleport.notifications.v1.NotificationService.UpsertUserNotificationState:output_type -> teleport.notifications.v1.UserNotificationState
- 12, // 19: teleport.notifications.v1.NotificationService.UpsertUserLastSeenNotification:output_type -> teleport.notifications.v1.UserLastSeenNotification
- 13, // [13:20] is the sub-list for method output_type
- 6, // [6:13] is the sub-list for method input_type
- 6, // [6:6] is the sub-list for extension type_name
- 6, // [6:6] is the sub-list for extension extendee
- 0, // [0:6] is the sub-list for field type_name
+ 9, // 0: teleport.notifications.v1.CreateUserNotificationRequest.notification:type_name -> teleport.notifications.v1.Notification
+ 3, // 1: teleport.notifications.v1.ListNotificationsRequest.filters:type_name -> teleport.notifications.v1.NotificationFilters
+ 9, // 2: teleport.notifications.v1.ListNotificationsResponse.notifications:type_name -> teleport.notifications.v1.Notification
+ 10, // 3: teleport.notifications.v1.ListNotificationsResponse.user_last_seen_notification_timestamp:type_name -> google.protobuf.Timestamp
+ 11, // 4: teleport.notifications.v1.CreateGlobalNotificationRequest.global_notification:type_name -> teleport.notifications.v1.GlobalNotification
+ 12, // 5: teleport.notifications.v1.UpsertUserNotificationStateRequest.user_notification_state:type_name -> teleport.notifications.v1.UserNotificationState
+ 13, // 6: teleport.notifications.v1.UpsertUserLastSeenNotificationRequest.user_last_seen_notification:type_name -> teleport.notifications.v1.UserLastSeenNotification
+ 0, // 7: teleport.notifications.v1.NotificationService.CreateUserNotification:input_type -> teleport.notifications.v1.CreateUserNotificationRequest
+ 1, // 8: teleport.notifications.v1.NotificationService.DeleteUserNotification:input_type -> teleport.notifications.v1.DeleteUserNotificationRequest
+ 5, // 9: teleport.notifications.v1.NotificationService.CreateGlobalNotification:input_type -> teleport.notifications.v1.CreateGlobalNotificationRequest
+ 6, // 10: teleport.notifications.v1.NotificationService.DeleteGlobalNotification:input_type -> teleport.notifications.v1.DeleteGlobalNotificationRequest
+ 2, // 11: teleport.notifications.v1.NotificationService.ListNotifications:input_type -> teleport.notifications.v1.ListNotificationsRequest
+ 7, // 12: teleport.notifications.v1.NotificationService.UpsertUserNotificationState:input_type -> teleport.notifications.v1.UpsertUserNotificationStateRequest
+ 8, // 13: teleport.notifications.v1.NotificationService.UpsertUserLastSeenNotification:input_type -> teleport.notifications.v1.UpsertUserLastSeenNotificationRequest
+ 9, // 14: teleport.notifications.v1.NotificationService.CreateUserNotification:output_type -> teleport.notifications.v1.Notification
+ 14, // 15: teleport.notifications.v1.NotificationService.DeleteUserNotification:output_type -> google.protobuf.Empty
+ 11, // 16: teleport.notifications.v1.NotificationService.CreateGlobalNotification:output_type -> teleport.notifications.v1.GlobalNotification
+ 14, // 17: teleport.notifications.v1.NotificationService.DeleteGlobalNotification:output_type -> google.protobuf.Empty
+ 4, // 18: teleport.notifications.v1.NotificationService.ListNotifications:output_type -> teleport.notifications.v1.ListNotificationsResponse
+ 12, // 19: teleport.notifications.v1.NotificationService.UpsertUserNotificationState:output_type -> teleport.notifications.v1.UserNotificationState
+ 13, // 20: teleport.notifications.v1.NotificationService.UpsertUserLastSeenNotification:output_type -> teleport.notifications.v1.UserLastSeenNotification
+ 14, // [14:21] is the sub-list for method output_type
+ 7, // [7:14] is the sub-list for method input_type
+ 7, // [7:7] is the sub-list for extension type_name
+ 7, // [7:7] is the sub-list for extension extendee
+ 0, // [0:7] is the sub-list for field type_name
}
func init() { file_teleport_notifications_v1_notifications_service_proto_init() }
@@ -749,7 +840,7 @@ func file_teleport_notifications_v1_notifications_service_proto_init() {
}
}
file_teleport_notifications_v1_notifications_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*ListNotificationsResponse); i {
+ switch v := v.(*NotificationFilters); i {
case 0:
return &v.state
case 1:
@@ -761,7 +852,7 @@ func file_teleport_notifications_v1_notifications_service_proto_init() {
}
}
file_teleport_notifications_v1_notifications_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*CreateGlobalNotificationRequest); i {
+ switch v := v.(*ListNotificationsResponse); i {
case 0:
return &v.state
case 1:
@@ -773,7 +864,7 @@ func file_teleport_notifications_v1_notifications_service_proto_init() {
}
}
file_teleport_notifications_v1_notifications_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*DeleteGlobalNotificationRequest); i {
+ switch v := v.(*CreateGlobalNotificationRequest); i {
case 0:
return &v.state
case 1:
@@ -785,7 +876,7 @@ func file_teleport_notifications_v1_notifications_service_proto_init() {
}
}
file_teleport_notifications_v1_notifications_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*UpsertUserNotificationStateRequest); i {
+ switch v := v.(*DeleteGlobalNotificationRequest); i {
case 0:
return &v.state
case 1:
@@ -797,6 +888,18 @@ func file_teleport_notifications_v1_notifications_service_proto_init() {
}
}
file_teleport_notifications_v1_notifications_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*UpsertUserNotificationStateRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_teleport_notifications_v1_notifications_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UpsertUserLastSeenNotificationRequest); i {
case 0:
return &v.state
@@ -815,7 +918,7 @@ func file_teleport_notifications_v1_notifications_service_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_teleport_notifications_v1_notifications_service_proto_rawDesc,
NumEnums: 0,
- NumMessages: 8,
+ NumMessages: 9,
NumExtensions: 0,
NumServices: 1,
},
diff --git a/api/proto/teleport/notifications/v1/notifications_service.proto b/api/proto/teleport/notifications/v1/notifications_service.proto
index ce3f53579c3ef..a32a5921ab7e5 100644
--- a/api/proto/teleport/notifications/v1/notifications_service.proto
+++ b/api/proto/teleport/notifications/v1/notifications_service.proto
@@ -70,6 +70,18 @@ message ListNotificationsRequest {
int32 page_size = 1;
// page_token is the next_page_token value returned from a previous ListUserNotifications request, if any.
string page_token = 2;
+ // filters specify search criteria to limit which notifications should be returned. If omitted, the default behavior will be to list all notifications.
+ NotificationFilters filters = 3;
+}
+
+// NotificationFilters provide a mechanism to refine ListNotification results.
+message NotificationFilters {
+ // username is the username of the user the notifications being listed are for.
+ string username = 1;
+ // global_only is whether to only list global notifications (notifications capable of targetting multiple users).
+ bool global_only = 2;
+ // user_created_only is whether to only list user-created notifications (ie. notifications created by an admin via the tctl interface).
+ bool user_created_only = 3;
}
// ListNotificationsResponse is the response from listing a user's notifications.
diff --git a/api/types/constants.go b/api/types/constants.go
index 4c3584e825513..e7f3bae75a657 100644
--- a/api/types/constants.go
+++ b/api/types/constants.go
@@ -1038,6 +1038,8 @@ const (
NotificationClickedLabel = TeleportInternalLabelPrefix + "clicked"
// NotificationScope is the label which contains the scope of the notification, either "user" or "global"
NotificationScope = TeleportInternalLabelPrefix + "scope"
+ // NotificationTextContentLabel is the label which contains the text content of a user-created notification.
+ NotificationTextContentLabel = TeleportInternalLabelPrefix + "content"
// NotificationDefaultInformationalSubKind is the default subkind for an informational notification.
NotificationDefaultInformationalSubKind = "default-informational"
diff --git a/lib/auth/authclient/clt.go b/lib/auth/authclient/clt.go
index 0fbf9115f425c..2547b8884e299 100644
--- a/lib/auth/authclient/clt.go
+++ b/lib/auth/authclient/clt.go
@@ -712,21 +712,36 @@ func (c *Client) UpsertUserLastSeenNotification(ctx context.Context, username st
}
// CreateGlobalNotification creates a global notification.
-func (c *Client) CreateGlobalNotification(ctx context.Context, globalNotification *notificationsv1.GlobalNotification) (*notificationsv1.GlobalNotification, error) {
- // TODO(rudream): implement client methods for notifications
- return nil, trace.NotImplemented(notImplementedMessage)
+func (c *Client) CreateGlobalNotification(ctx context.Context, gn *notificationsv1.GlobalNotification) (*notificationsv1.GlobalNotification, error) {
+ rsp, err := c.APIClient.CreateGlobalNotification(ctx, ¬ificationsv1.CreateGlobalNotificationRequest{
+ GlobalNotification: gn,
+ })
+ return rsp, trace.Wrap(err)
}
// CreateUserNotification creates a user-specific notification.
func (c *Client) CreateUserNotification(ctx context.Context, notification *notificationsv1.Notification) (*notificationsv1.Notification, error) {
- // TODO(rudream): implement client methods for notifications
- return nil, trace.NotImplemented(notImplementedMessage)
+ rsp, err := c.APIClient.CreateUserNotification(ctx, ¬ificationsv1.CreateUserNotificationRequest{
+ Notification: notification,
+ })
+ return rsp, trace.Wrap(err)
}
// DeleteGlobalNotification deletes a global notification.
func (c *Client) DeleteGlobalNotification(ctx context.Context, notificationId string) error {
- // TODO(rudream): implement client methods for notifications
- return trace.NotImplemented(notImplementedMessage)
+ err := c.APIClient.DeleteGlobalNotification(ctx, ¬ificationsv1.DeleteGlobalNotificationRequest{
+ NotificationId: notificationId,
+ })
+ return trace.Wrap(err)
+}
+
+// DeleteUserNotification not implemented: can only be called locally.
+func (c *Client) DeleteUserNotification(ctx context.Context, username string, notificationId string) error {
+ err := c.APIClient.DeleteUserNotification(ctx, ¬ificationsv1.DeleteUserNotificationRequest{
+ Username: username,
+ NotificationId: notificationId,
+ })
+ return trace.Wrap(err)
}
// DeleteAllGlobalNotifications not implemented: can only be called locally.
@@ -754,11 +769,6 @@ func (c *Client) DeleteUserLastSeenNotification(ctx context.Context, username st
return trace.NotImplemented(notImplementedMessage)
}
-// DeleteUserNotification not implemented: can only be called locally.
-func (c *Client) DeleteUserNotification(ctx context.Context, username string, notificationId string) error {
- return trace.NotImplemented(notImplementedMessage)
-}
-
// DeleteUserNotificationState not implemented: can only be called locally.
func (c *Client) DeleteUserNotificationState(ctx context.Context, username string, notificationId string) error {
return trace.NotImplemented(notImplementedMessage)
diff --git a/lib/auth/notifications/notificationsv1/service.go b/lib/auth/notifications/notificationsv1/service.go
index d62976f59d145..7edcbbe337e1c 100644
--- a/lib/auth/notifications/notificationsv1/service.go
+++ b/lib/auth/notifications/notificationsv1/service.go
@@ -25,6 +25,7 @@ import (
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
+ "google.golang.org/protobuf/types/known/emptypb"
"github.com/gravitational/teleport/api/client"
apidefaults "github.com/gravitational/teleport/api/defaults"
@@ -104,6 +105,17 @@ func NewService(cfg ServiceConfig) (*Service, error) {
// ListNotifications returns a paginated list of notifications which match the user.
func (s *Service) ListNotifications(ctx context.Context, req *notificationsv1.ListNotificationsRequest) (*notificationsv1.ListNotificationsResponse, error) {
+ if req.Filters != nil {
+ if req.Filters.GlobalOnly {
+ return s.listGlobalNotifications(ctx, req.Filters.UserCreatedOnly, req.PageToken, req.PageSize)
+ }
+ if req.Filters.Username != "" {
+ return s.listUserSpecificNotificationsForUser(ctx, req.Filters.Username, req.Filters.UserCreatedOnly, req.PageToken, req.PageSize)
+ }
+
+ return nil, trace.BadParameter("Invalid filters were provided, exactly one of GlobalOnly or Username must be defined.")
+ }
+
authCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
@@ -479,3 +491,191 @@ func (s *Service) UpsertUserLastSeenNotification(ctx context.Context, req *notif
return out, nil
}
+
+// CreateGlobalNotification creates a global notification.
+func (s *Service) CreateGlobalNotification(ctx context.Context, req *notificationsv1.CreateGlobalNotificationRequest) (*notificationsv1.GlobalNotification, error) {
+ authCtx, err := s.authorizer.Authorize(ctx)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ if err := authCtx.CheckAccessToKind(types.KindNotification, types.VerbCreate); err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ out, err := s.backend.CreateGlobalNotification(ctx, req.GlobalNotification)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ return out, nil
+}
+
+// CreateUserNotification creates a user-specific notification.
+func (s *Service) CreateUserNotification(ctx context.Context, req *notificationsv1.CreateUserNotificationRequest) (*notificationsv1.Notification, error) {
+ if req.Username == "" {
+ return nil, trace.BadParameter("missing username")
+ }
+
+ authCtx, err := s.authorizer.Authorize(ctx)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ if err := authCtx.CheckAccessToKind(types.KindNotification, types.VerbCreate); err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ out, err := s.backend.CreateUserNotification(ctx, req.Notification)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ return out, nil
+}
+
+// DeleteGlobalNotification deletes a global notification.
+func (s *Service) DeleteGlobalNotification(ctx context.Context, req *notificationsv1.DeleteGlobalNotificationRequest) (*emptypb.Empty, error) {
+ authCtx, err := s.authorizer.Authorize(ctx)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ if err := authCtx.CheckAccessToKind(types.KindNotification, types.VerbDelete); err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ err = s.backend.DeleteGlobalNotification(ctx, req.NotificationId)
+ return nil, trace.Wrap(err)
+}
+
+// DeleteUserNotification deletes a user-specific notification.
+func (s *Service) DeleteUserNotification(ctx context.Context, req *notificationsv1.DeleteUserNotificationRequest) (*emptypb.Empty, error) {
+ authCtx, err := s.authorizer.Authorize(ctx)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ if err := authCtx.CheckAccessToKind(types.KindNotification, types.VerbDelete); err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ err = s.backend.DeleteUserNotification(ctx, req.Username, req.NotificationId)
+ return nil, trace.Wrap(err)
+}
+
+// listUserSpecificNotificationsForUser returns a paginated list of all user-specific notifications for a user. This should only be used by admins.
+func (s *Service) listUserSpecificNotificationsForUser(ctx context.Context, username string, userCreatedOnly bool, pageToken string, pageSize int32) (*notificationsv1.ListNotificationsResponse, error) {
+ if username == "" {
+ return nil, trace.BadParameter("missing username")
+ }
+
+ authCtx, err := s.authorizer.Authorize(ctx)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ if !authz.HasBuiltinRole(*authCtx, string(types.RoleAdmin)) {
+ return nil, trace.AccessDenied("only RoleAdmin can list notifications for a specific user")
+ }
+
+ if err := authCtx.CheckAccessToKind(types.KindNotification, types.VerbList); err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ stream := stream.FilterMap(
+ s.userNotificationCache.StreamUserNotifications(ctx, username, pageToken),
+ func(n *notificationsv1.Notification) (*notificationsv1.Notification, bool) {
+ // If only user-created notifications are requested, filter by the user-creatd subkinds.
+ if userCreatedOnly &&
+ n.GetSubKind() != types.NotificationUserCreatedInformationalSubKind &&
+ n.GetSubKind() != types.NotificationUserCreatedWarningSubKind {
+ return nil, false
+ }
+
+ return n, true
+ })
+
+ var notifications []*notificationsv1.Notification
+ var nextKey string
+
+ for stream.Next() {
+ item := stream.Item()
+ if item != nil {
+ notifications = append(notifications, item)
+ if len(notifications) == int(pageSize) {
+ nextKey = item.GetMetadata().GetName()
+ break
+ }
+ }
+ }
+
+ return ¬ificationsv1.ListNotificationsResponse{
+ Notifications: notifications,
+ NextPageToken: nextKey,
+ }, nil
+}
+
+// listGlobalNotifications returns a paginated list of all global notifications. This should only be used by admins.
+func (s *Service) listGlobalNotifications(ctx context.Context, userCreatedOnly bool, pageToken string, pageSize int32) (*notificationsv1.ListNotificationsResponse, error) {
+ authCtx, err := s.authorizer.Authorize(ctx)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ if !authz.HasBuiltinRole(*authCtx, string(types.RoleAdmin)) {
+ return nil, trace.AccessDenied("only RoleAdmin can list all global notifications")
+ }
+
+ stream := stream.FilterMap(
+ s.globalNotificationCache.StreamGlobalNotifications(ctx, pageToken),
+ func(gn *notificationsv1.GlobalNotification) (*notificationsv1.GlobalNotification, bool) {
+ // If only user-created notifications are requested, filter by the user-creatd subkinds.
+ if userCreatedOnly &&
+ gn.GetSpec().GetNotification().GetSubKind() != types.NotificationUserCreatedInformationalSubKind &&
+ gn.GetSpec().GetNotification().GetSubKind() != types.NotificationUserCreatedWarningSubKind {
+ return nil, false
+ }
+
+ return gn, true
+ })
+
+ var notifications []*notificationsv1.Notification
+ var nextKey string
+
+ for stream.Next() {
+ item := stream.Item()
+ if item != nil {
+ notification := item.GetSpec().GetNotification()
+ notification.Metadata.Name = item.GetMetadata().GetName()
+
+ notifications = append(notifications, notification)
+
+ if len(notifications) == int(pageSize) {
+ nextKey = item.GetMetadata().GetName()
+ break
+ }
+ }
+ }
+
+ return ¬ificationsv1.ListNotificationsResponse{
+ Notifications: notifications,
+ NextPageToken: nextKey,
+ }, nil
+}
diff --git a/lib/services/presets.go b/lib/services/presets.go
index f66b442934f93..d1ad8236da01c 100644
--- a/lib/services/presets.go
+++ b/lib/services/presets.go
@@ -177,6 +177,7 @@ func NewPresetEditorRole() types.Role {
types.NewRule(types.KindAppServer, RW()),
types.NewRule(types.KindVnetConfig, RW()),
types.NewRule(types.KindAccessGraphSettings, RW()),
+ types.NewRule(types.KindNotification, RW()),
},
},
},
@@ -282,6 +283,7 @@ func NewPresetAuditorRole() types.Role {
types.NewRule(types.KindInstance, RO()),
types.NewRule(types.KindSecurityReport, append(RO(), types.VerbUse)),
types.NewRule(types.KindAuditQuery, append(RO(), types.VerbUse)),
+ types.NewRule(types.KindNotification, RO()),
},
},
},
diff --git a/lib/web/ui/notification.go b/lib/web/ui/notification.go
index c1b8c772d4686..1c75a00a10db5 100644
--- a/lib/web/ui/notification.go
+++ b/lib/web/ui/notification.go
@@ -26,12 +26,13 @@ import (
)
type Notification struct {
- Id string `json:"id"`
- Title string `json:"title"`
- SubKind string `json:"subKind"`
- Created time.Time `json:"created"`
- Clicked bool `json:"clicked"`
- Labels []Label `json:"labels"`
+ ID string `json:"id"`
+ Title string `json:"title"`
+ SubKind string `json:"subKind"`
+ Created time.Time `json:"created"`
+ Clicked bool `json:"clicked"`
+ TextContent string `json:"textContent,omitempty"`
+ Labels []Label `json:"labels"`
}
// MakeNotification creates a notification object for the WebUI.
@@ -41,11 +42,12 @@ func MakeNotification(notification *notificationsv1.Notification) Notification {
clicked := notification.Metadata.GetLabels()[types.NotificationClickedLabel] == "true"
return Notification{
- Id: notification.Metadata.GetName(),
- Title: notification.Metadata.GetLabels()[types.NotificationTitleLabel],
- SubKind: notification.SubKind,
- Created: notification.Spec.Created.AsTime(),
- Clicked: clicked,
- Labels: labels,
+ ID: notification.Metadata.GetName(),
+ Title: notification.Metadata.GetLabels()[types.NotificationTitleLabel],
+ SubKind: notification.SubKind,
+ Created: notification.Spec.Created.AsTime(),
+ Clicked: clicked,
+ TextContent: notification.Metadata.GetLabels()[types.NotificationTextContentLabel],
+ Labels: labels,
}
}
diff --git a/tool/tctl/common/cmds.go b/tool/tctl/common/cmds.go
index 9a78c4191d28f..6074006e8ef61 100644
--- a/tool/tctl/common/cmds.go
+++ b/tool/tctl/common/cmds.go
@@ -57,6 +57,7 @@ func Commands() []CLICommand {
&IdPCommand{},
&accessmonitoring.Command{},
&plugin.PluginsCommand{},
+ &NotificationCommand{},
&configure.SSOConfigureCommand{},
&tester.SSOTestCommand{},
&fido2Command{},
diff --git a/tool/tctl/common/helpers_test.go b/tool/tctl/common/helpers_test.go
index 815cb78593d17..a9c17e4cef4fd 100644
--- a/tool/tctl/common/helpers_test.go
+++ b/tool/tctl/common/helpers_test.go
@@ -133,6 +133,16 @@ func runIdPSAMLCommand(t *testing.T, client *authclient.Client, args []string) e
return runCommand(t, client, command, args)
}
+func runNotificationsCommand(t *testing.T, client *authclient.Client, args []string) (*bytes.Buffer, error) {
+ var stdoutBuff bytes.Buffer
+ command := &NotificationCommand{
+ stdout: &stdoutBuff,
+ }
+
+ args = append([]string{"notifications"}, args...)
+ return &stdoutBuff, runCommand(t, client, command, args)
+}
+
func mustDecodeJSON[T any](t *testing.T, r io.Reader) T {
var out T
err := json.NewDecoder(r).Decode(&out)
diff --git a/tool/tctl/common/notification_command.go b/tool/tctl/common/notification_command.go
new file mode 100644
index 0000000000000..640b2e8b32b1c
--- /dev/null
+++ b/tool/tctl/common/notification_command.go
@@ -0,0 +1,313 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package common
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "time"
+
+ "github.com/alecthomas/kingpin/v2"
+ "github.com/gravitational/trace"
+ "github.com/gravitational/trace/trail"
+
+ "github.com/gravitational/teleport"
+ "github.com/gravitational/teleport/api/defaults"
+ headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
+ notificationspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
+ "github.com/gravitational/teleport/api/mfa"
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/lib/asciitable"
+ "github.com/gravitational/teleport/lib/auth/authclient"
+ "github.com/gravitational/teleport/lib/service/servicecfg"
+ "github.com/gravitational/teleport/lib/utils"
+)
+
+// NotificationCommand implements the `tctl notifications` family of commands.
+type NotificationCommand struct {
+ ls *kingpin.CmdClause
+ rm *kingpin.CmdClause
+ create *kingpin.CmdClause
+
+ format string
+ user string
+ roles []string
+ requireAllRoles bool
+ warning bool
+ // allNotifications determines whether the list returned will include all notifications and not just those created manually by a user via tctl.
+ allNotifications bool
+
+ title string
+ content string
+
+ // stdout allows to switch the standard output source. Used in tests.
+ stdout io.Writer
+}
+
+// Initialize allows NotificationCommand command to plug itself into the CLI parser
+func (n *NotificationCommand) Initialize(app *kingpin.Application, _ *servicecfg.Config) {
+ notif := app.Command("notifications", "Manage cluster notifications.")
+
+ n.create = notif.Command("create", "Create a cluster notification.").Alias("add")
+ n.create.Flag("user", "Target a specific user.").StringVar(&n.user)
+ n.create.Flag("roles", "Target a specific set of roles. By default, this will target all users with any of the provided roles, use --require-all-roles to exclusively target users with all of them.").StringsVar(&n.roles)
+ n.create.Flag("require-all-roles", "Set whether this notification should target users who have all of the provided roles.").BoolVar(&n.requireAllRoles)
+ n.create.Flag("title", "Set the notification's title.").Short('t').Required().StringVar(&n.title)
+ n.create.Flag("content", "Set the notification's content.").Required().StringVar(&n.content)
+ n.create.Flag("warning", "Set whether this notification is a warning notification.").BoolVar(&n.warning)
+
+ n.ls = notif.Command("ls", "List notifications which were manually created using `tctl notifications create`. By default, this will list notifications capable of targeting multiple users, such as role-based ones. To list notifications directed only at a specific user, use the --user flag. To include notifications generated by Teleport, use --all.")
+ n.ls.Flag("user", "Set which user to list user-specific notifications for.").StringVar(&n.user)
+ n.ls.Flag("format", "Output format, 'yaml', 'json', or 'text'").Default(teleport.Text).EnumVar(&n.format, teleport.YAML, teleport.JSON, teleport.Text)
+ n.ls.Flag("all", "Set whether all notifications should be included, including those generated by Teleport, as opposed to solely those created using `tctl notifications create`.").BoolVar(&n.allNotifications)
+
+ n.rm = notif.Command("rm", "Remove a cluster notification.").Alias("remove")
+ n.rm.Flag("user", "The user the notification to remove belongs to, if any.").StringVar(&n.user)
+ n.rm.Arg("id", "The ID of the notification to remove.").Required().StringVar(&n.title)
+
+ if n.stdout == nil {
+ n.stdout = os.Stdout
+ }
+}
+
+// TryRun takes the CLI command as an argument and executes it.
+func (n *NotificationCommand) TryRun(ctx context.Context, cmd string, client *authclient.Client) (match bool, err error) {
+ nc := client.NotificationServiceClient()
+
+ switch cmd {
+ case n.create.FullCommand():
+ err = n.Create(ctx, client)
+ case n.ls.FullCommand():
+ err = n.List(ctx, nc)
+ case n.rm.FullCommand():
+ err = n.Remove(ctx, client)
+ default:
+ return false, nil
+ }
+ return true, trace.Wrap(err)
+}
+
+// Create creates a new notification.
+func (n *NotificationCommand) Create(ctx context.Context, client *authclient.Client) error {
+ meta := &headerv1.Metadata{
+ Labels: map[string]string{
+ types.NotificationTitleLabel: n.title,
+ types.NotificationTextContentLabel: n.content,
+ },
+ }
+
+ subKind := types.NotificationUserCreatedInformationalSubKind
+ if n.warning {
+ subKind = types.NotificationUserCreatedWarningSubKind
+ }
+
+ // Prompt for admin action MFA re-auth.
+ mfaResponse, err := mfa.PerformAdminActionMFACeremony(ctx, client.PerformMFACeremony, true /*allowReuse*/)
+ if err == nil {
+ ctx = mfa.ContextWithMFAResponse(ctx, mfaResponse)
+ } else if !errors.Is(err, &mfa.ErrMFANotRequired) && !errors.Is(err, &mfa.ErrMFANotSupported) {
+ return trace.Wrap(err)
+ }
+
+ nc := client.NotificationServiceClient()
+
+ if n.user != "" {
+ if len(n.roles) != 0 || n.requireAllRoles {
+ return trace.BadParameter("roles cannot be configured for a notification which targets a specific user")
+ }
+
+ created, err := nc.CreateUserNotification(ctx, ¬ificationspb.CreateUserNotificationRequest{
+ Username: n.user,
+ Notification: ¬ificationspb.Notification{
+ Kind: types.KindNotification,
+ SubKind: subKind,
+ Metadata: meta,
+ Spec: ¬ificationspb.NotificationSpec{
+ Username: n.user,
+ },
+ },
+ })
+
+ if err != nil {
+ return trail.FromGRPC(err)
+ }
+
+ fmt.Fprintf(n.stdout, "Created notification %s for user %s\n", created.GetMetadata().GetName(), n.user)
+ return nil
+ }
+
+ if len(n.roles) != 0 {
+ created, err := nc.CreateGlobalNotification(ctx, ¬ificationspb.CreateGlobalNotificationRequest{
+ GlobalNotification: ¬ificationspb.GlobalNotification{
+ Kind: types.KindGlobalNotification,
+ Spec: ¬ificationspb.GlobalNotificationSpec{
+ Matcher: ¬ificationspb.GlobalNotificationSpec_ByRoles{
+ ByRoles: ¬ificationspb.ByRoles{
+ Roles: n.roles,
+ },
+ },
+ MatchAllConditions: n.requireAllRoles,
+ Notification: ¬ificationspb.Notification{
+ Kind: types.KindNotification,
+ SubKind: subKind,
+ Metadata: meta,
+ Spec: ¬ificationspb.NotificationSpec{},
+ },
+ },
+ },
+ })
+
+ if err != nil {
+ return trail.FromGRPC(err)
+ }
+
+ if n.requireAllRoles {
+ fmt.Fprintf(n.stdout, "Created notification %s for users with all of the following roles: %s\n", created.GetMetadata().GetName(), n.roles)
+ return nil
+ }
+
+ fmt.Fprintf(n.stdout, "Created notification %s for users with one or more of the following roles: %s\n", created.GetMetadata().GetName(), n.roles)
+ return nil
+ }
+
+ if n.requireAllRoles {
+ return trace.BadParameter("--require-all-roles was set, but no --roles were provided")
+ }
+
+ // If roles weren't provided, default to targeting all users.
+ created, err := nc.CreateGlobalNotification(ctx, ¬ificationspb.CreateGlobalNotificationRequest{
+ GlobalNotification: ¬ificationspb.GlobalNotification{
+ Kind: types.KindGlobalNotification,
+ Spec: ¬ificationspb.GlobalNotificationSpec{
+ Matcher: ¬ificationspb.GlobalNotificationSpec_All{
+ All: true,
+ },
+ Notification: ¬ificationspb.Notification{
+ Kind: types.KindNotification,
+ SubKind: subKind,
+ Metadata: meta,
+ Spec: ¬ificationspb.NotificationSpec{},
+ },
+ },
+ },
+ })
+ if err != nil {
+ return trail.FromGRPC(err)
+ }
+
+ fmt.Fprintf(n.stdout, "Created notification %s for all users\n", created.GetMetadata().GetName())
+ return nil
+}
+
+func (n *NotificationCommand) List(ctx context.Context, client notificationspb.NotificationServiceClient) error {
+ var result []*notificationspb.Notification
+ var pageToken string
+ for {
+ var resp *notificationspb.ListNotificationsResponse
+ var err error
+
+ // If a user was specified, list user-specific notifications for them, if not, default to listing global notifications.
+ if n.user != "" {
+ resp, err = client.ListNotifications(ctx, ¬ificationspb.ListNotificationsRequest{
+ PageSize: defaults.DefaultChunkSize,
+ PageToken: pageToken,
+ Filters: ¬ificationspb.NotificationFilters{
+ Username: n.user,
+ UserCreatedOnly: !n.allNotifications,
+ },
+ })
+ if err != nil {
+ return trace.Wrap(err)
+ }
+ } else {
+ resp, err = client.ListNotifications(ctx, ¬ificationspb.ListNotificationsRequest{
+ PageSize: defaults.DefaultChunkSize,
+ PageToken: pageToken,
+ Filters: ¬ificationspb.NotificationFilters{
+ GlobalOnly: true,
+ UserCreatedOnly: !n.allNotifications,
+ },
+ })
+ if err != nil {
+ return trace.Wrap(err)
+ }
+ }
+
+ result = append(result, resp.Notifications...)
+ pageToken = resp.GetNextPageToken()
+ if pageToken == "" {
+ break
+ }
+ }
+
+ displayNotifications(n.format, result, n.stdout)
+ return nil
+}
+
+func displayNotifications(format string, notifications []*notificationspb.Notification, w io.Writer) {
+ switch format {
+ case teleport.Text:
+ table := asciitable.MakeTable([]string{"ID", "Created", "Expires", "Title"})
+ for _, n := range notifications {
+ table.AddRow([]string{
+ n.GetMetadata().GetName(),
+ n.GetSpec().GetCreated().AsTime().Format(time.RFC822),
+ n.GetMetadata().GetExpires().AsTime().Format(time.RFC822),
+ n.GetMetadata().GetLabels()[types.NotificationTitleLabel],
+ })
+ }
+ fmt.Fprint(w, table.AsBuffer().String())
+ case teleport.JSON:
+ utils.WriteJSONArray(w, notifications)
+ case teleport.YAML:
+ utils.WriteYAML(w, notifications)
+ default:
+ // Do nothing, kingpin validates the --format flag before we ever get here.
+ }
+}
+
+// Remove removes a notification.
+func (n *NotificationCommand) Remove(ctx context.Context, client *authclient.Client) error {
+ // Prompt for admin action MFA re-auth.
+ mfaResponse, err := mfa.PerformAdminActionMFACeremony(ctx, client.PerformMFACeremony, true /*allowReuse*/)
+ if err == nil {
+ ctx = mfa.ContextWithMFAResponse(ctx, mfaResponse)
+ } else if !errors.Is(err, &mfa.ErrMFANotRequired) && !errors.Is(err, &mfa.ErrMFANotSupported) {
+ return trace.Wrap(err)
+ }
+
+ nc := client.NotificationServiceClient()
+
+ switch {
+ case n.user != "":
+ _, err = nc.DeleteUserNotification(ctx, ¬ificationspb.DeleteUserNotificationRequest{
+ Username: n.user,
+ NotificationId: n.title,
+ })
+ default:
+ _, err = nc.DeleteGlobalNotification(ctx, ¬ificationspb.DeleteGlobalNotificationRequest{
+ NotificationId: n.title,
+ })
+ }
+
+ return trail.FromGRPC(err)
+}
diff --git a/tool/tctl/common/notification_command_test.go b/tool/tctl/common/notification_command_test.go
new file mode 100644
index 0000000000000..9309e45bfd727
--- /dev/null
+++ b/tool/tctl/common/notification_command_test.go
@@ -0,0 +1,113 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package common
+
+import (
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/gravitational/teleport/integration/helpers"
+ "github.com/gravitational/teleport/lib/config"
+ "github.com/gravitational/teleport/tool/teleport/testenv"
+)
+
+// TestNotificationCommandCRUD tests creating, listing, and deleting notifications via the `tctl notifications` commands.
+func TestNotificationCommmandCRUD(t *testing.T) {
+ dynAddr := helpers.NewDynamicServiceAddr(t)
+ fileConfig := &config.FileConfig{
+ Global: config.Global{
+ DataDir: t.TempDir(),
+ },
+ Auth: config.Auth{
+ Service: config.Service{
+ EnabledFlag: "true",
+ ListenAddress: dynAddr.AuthAddr,
+ },
+ },
+ }
+ process := makeAndRunTestAuthServer(t, withFileConfig(fileConfig), withFileDescriptors(dynAddr.Descriptors))
+
+ clt := testenv.MakeDefaultAuthClient(t, process)
+
+ auditorUsername := "auditor-user"
+ managerUsername := "manager-user"
+
+ // Test creating a user-specific notification for auditor user.
+ buf, err := runNotificationsCommand(t, clt, []string{"create", "--user", auditorUsername, "--title", "auditor notification", "--content", "This is a test notification."})
+ require.NoError(t, err)
+ require.Contains(t, buf.String(), "for user auditor-user")
+ auditorUserNotificationId := strings.Split(buf.String(), " ")[2]
+
+ // Test creating a user-specific notification for manager user.
+ buf, err = runNotificationsCommand(t, clt, []string{"create", "--user", managerUsername, "--title", "manager notification", "--content", "This is a test notification."})
+ require.NoError(t, err)
+ require.Contains(t, buf.String(), "for user manager-user")
+
+ // Test creating a global notification for users with the test-1 role.
+ buf, err = runNotificationsCommand(t, clt, []string{"create", "--roles", "test-1", "--title", "test-1 notification", "--content", "This is a test notification."})
+ require.NoError(t, err)
+ require.Contains(t, buf.String(), "for users with one or more of the following roles: [test-1]")
+ globalNotificationId := strings.Split(buf.String(), " ")[2]
+
+ // We periodically check with a timeout since it can take some time for the item to be replicated in the cache and be available for listing.
+ require.EventuallyWithT(t, func(collectT *assert.CollectT) {
+ // List notifications for auditor and verify that auditor notification exists.
+ buf, err = runNotificationsCommand(t, clt, []string{"ls", "--user", auditorUsername})
+ assert.NoError(collectT, err)
+ assert.Contains(collectT, buf.String(), "auditor notification")
+ assert.NotContains(collectT, buf.String(), "manager notification")
+
+ // List notifications for manager and verify output.
+ buf, err = runNotificationsCommand(t, clt, []string{"ls", "--user", managerUsername})
+ assert.NoError(collectT, err)
+ assert.Contains(collectT, buf.String(), "manager notification")
+ assert.NotContains(collectT, buf.String(), "auditor notification")
+
+ // List global notifications and verify that test-1 notification exists.
+ buf, err = runNotificationsCommand(t, clt, []string{"ls"})
+ assert.NoError(collectT, err)
+ assert.Contains(collectT, buf.String(), "test-1 notification")
+ assert.NotContains(collectT, buf.String(), "auditor notification")
+ assert.NotContains(collectT, buf.String(), "manager notification")
+
+ }, 3*time.Second, 100*time.Millisecond)
+
+ // Delete the auditor's user-specific notification.
+ _, err = runNotificationsCommand(t, clt, []string{"rm", auditorUserNotificationId, "--user", auditorUsername})
+ require.NoError(t, err)
+ // Delete the global notification.
+ _, err = runNotificationsCommand(t, clt, []string{"rm", globalNotificationId})
+ require.NoError(t, err)
+
+ require.EventuallyWithT(t, func(collectT *assert.CollectT) {
+ // Verify that the global notification is no longer listed.
+ buf, err = runNotificationsCommand(t, clt, []string{"ls"})
+ assert.NoError(collectT, err)
+ assert.NotContains(collectT, buf.String(), "test-1 notification")
+
+ // Verify that the auditor notification is no longer listed.
+ buf, err = runNotificationsCommand(t, clt, []string{"ls", "--user", auditorUsername})
+ assert.NoError(collectT, err)
+ assert.NotContains(collectT, buf.String(), "auditor notification")
+ }, 3*time.Second, 100*time.Millisecond)
+}
diff --git a/web/packages/teleport/src/Notifications/Notification.tsx b/web/packages/teleport/src/Notifications/Notification.tsx
index 8398b2b01442d..ee4a0c93b5bdf 100644
--- a/web/packages/teleport/src/Notifications/Notification.tsx
+++ b/web/packages/teleport/src/Notifications/Notification.tsx
@@ -107,6 +107,27 @@ export function Notification({
// If the notification is unsupported or hidden, or if the view is "Unread" and the notification has been read,
// it should not be shown.
if (!content || (view === 'Unread' && notification.clicked)) {
+ // If this is a text content notification, the dialog should still be renderable. This is to prevent the text content dialog immediately disappearing
+ // when trying to open an unread text notification, since clicking on the notification instantly marks it as read.
+ if (content.kind == 'text') {
+ return (
+
+ );
+ }
return null;
}
@@ -135,6 +156,7 @@ export function Notification({
if (e.currentTarget.contains(e.target as HTMLElement)) {
if (content.kind === 'text') {
setShowTextContentDialog(true);
+ onMarkAsClicked();
return;
}
onMarkAsClicked();
diff --git a/web/packages/teleport/src/Notifications/notificationContentFactory.tsx b/web/packages/teleport/src/Notifications/notificationContentFactory.tsx
index bebf8ae862bf8..6b453357acd90 100644
--- a/web/packages/teleport/src/Notifications/notificationContentFactory.tsx
+++ b/web/packages/teleport/src/Notifications/notificationContentFactory.tsx
@@ -31,7 +31,6 @@ import { Label } from 'teleport/types';
*/
export function notificationContentFactory({
subKind,
- labels,
...notification
}: NotificationType): NotificationContent {
let notificationContent: NotificationContent;
@@ -39,11 +38,10 @@ export function notificationContentFactory({
switch (subKind) {
case NotificationSubKind.DefaultInformational:
case NotificationSubKind.UserCreatedInformational: {
- const textContent = getLabelValue(labels, 'content');
notificationContent = {
kind: 'text',
title: notification.title,
- textContent,
+ textContent: notification.textContent,
type: 'informational',
icon: Icons.Notification,
};
@@ -52,11 +50,10 @@ export function notificationContentFactory({
case NotificationSubKind.DefaultWarning:
case NotificationSubKind.UserCreatedWarning: {
- const textContent = getLabelValue(labels, 'content');
notificationContent = {
kind: 'text',
title: notification.title,
- textContent,
+ textContent: notification.textContent,
type: 'warning',
icon: Icons.Notification,
};
diff --git a/web/packages/teleport/src/services/notifications/notifications.ts b/web/packages/teleport/src/services/notifications/notifications.ts
index ed581e5b4365b..64ef809bb34dc 100644
--- a/web/packages/teleport/src/services/notifications/notifications.ts
+++ b/web/packages/teleport/src/services/notifications/notifications.ts
@@ -37,7 +37,8 @@ export class NotificationService {
return {
notifications: json.notifications
? json.notifications.map(notificationJson => {
- const { id, title, subKind, created, clicked } = notificationJson;
+ const { id, title, subKind, created, clicked, textContent } =
+ notificationJson;
const labels = notificationJson.labels || [];
return {
@@ -47,6 +48,7 @@ export class NotificationService {
createdDate: new Date(created),
clicked,
labels,
+ textContent,
};
})
: [],
diff --git a/web/packages/teleport/src/services/notifications/types.ts b/web/packages/teleport/src/services/notifications/types.ts
index 9573089193183..328288b3732db 100644
--- a/web/packages/teleport/src/services/notifications/types.ts
+++ b/web/packages/teleport/src/services/notifications/types.ts
@@ -77,6 +77,10 @@ export type Notification = {
labels: Label[];
/** title is the title of this notification. This can be overwritten in notificationContentFactory if needed. */
title: string;
+ /** textContent is the text content of this notification if it is merely a text notification (such as one created via `tctl notifications create`).
+ * This is the text that will be displayed in a dialog upon clicking the notification.
+ */
+ textContent?: string;
/** localNotification is whether this is a notification stored in a frontend store as opposed to a "real" notification
* from the notifications system. The reason for this is that some notification types (such as access lists) are not supported
* by the backend notifications system, and are instead generated entirely on the frontend.