mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
implement listing notifications for a user with matching (#39942)
This commit is contained in:
@@ -76,6 +76,7 @@ import (
|
||||
kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1"
|
||||
loginrulepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1"
|
||||
machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1"
|
||||
notificationsv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
|
||||
oktapb "github.com/gravitational/teleport/api/gen/proto/go/teleport/okta/v1"
|
||||
pluginspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/plugins/v1"
|
||||
presencepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1"
|
||||
@@ -111,6 +112,7 @@ type AuthServiceClient struct {
|
||||
assist.AssistServiceClient
|
||||
auditlogpb.AuditLogServiceClient
|
||||
userpreferencespb.UserPreferencesServiceClient
|
||||
notificationsv1pb.NotificationServiceClient
|
||||
}
|
||||
|
||||
// Client is a gRPC Client that connects to a Teleport Auth server either
|
||||
@@ -522,6 +524,7 @@ func (c *Client) dialGRPC(ctx context.Context, addr string) error {
|
||||
AssistServiceClient: assist.NewAssistServiceClient(c.conn),
|
||||
AuditLogServiceClient: auditlogpb.NewAuditLogServiceClient(c.conn),
|
||||
UserPreferencesServiceClient: userpreferencespb.NewUserPreferencesServiceClient(c.conn),
|
||||
NotificationServiceClient: notificationsv1pb.NewNotificationServiceClient(c.conn),
|
||||
}
|
||||
c.JoinServiceClient = NewJoinServiceClient(proto.NewJoinServiceClient(c.conn))
|
||||
|
||||
@@ -865,6 +868,11 @@ func (c *Client) WorkloadIdentityServiceClient() machineidv1pb.WorkloadIdentityS
|
||||
return machineidv1pb.NewWorkloadIdentityServiceClient(c.conn)
|
||||
}
|
||||
|
||||
// NotificationServiceClient returns a notification service client that can be used to fetch notifications.
|
||||
func (c *Client) NotificationServiceClient() notificationsv1pb.NotificationServiceClient {
|
||||
return notificationsv1pb.NewNotificationServiceClient(c.conn)
|
||||
}
|
||||
|
||||
// Ping gets basic info about the auth server.
|
||||
func (c *Client) Ping(ctx context.Context) (proto.PingResponse, error) {
|
||||
rsp, err := c.grpc.Ping(ctx, &proto.PingRequest{})
|
||||
@@ -4899,6 +4907,25 @@ func (c *Client) UpsertUserPreferences(ctx context.Context, in *userpreferencesp
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListNotifications returns a paginated list of notifications for the user.
|
||||
// This includes global notifications which match the user, as well as user-specific notifications for the user.
|
||||
func (c *Client) ListNotifications(ctx context.Context, req *notificationsv1pb.ListNotificationsRequest) (*notificationsv1pb.ListNotificationsResponse, error) {
|
||||
rsp, err := c.NotificationServiceClient().ListNotifications(ctx, req)
|
||||
return rsp, 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)
|
||||
return rsp, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// UpsertUserLastSeenNotification creates or updates a user's last seen notification timestamp.
|
||||
func (c *Client) UpsertUserLastSeenNotification(ctx context.Context, req *notificationsv1pb.UpsertUserLastSeenNotificationRequest) (*notificationsv1pb.UserLastSeenNotification, error) {
|
||||
rsp, err := c.NotificationServiceClient().UpsertUserLastSeenNotification(ctx, req)
|
||||
return rsp, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// ResourceUsageClient returns an unadorned Resource Usage service client,
|
||||
// using the underlying Auth gRPC connection.
|
||||
// Clients connecting to non-Enterprise clusters, or older Teleport versions,
|
||||
|
||||
@@ -183,8 +183,6 @@ type NotificationSpec struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// id is the UUID of the notification, in UUIDv7 format.
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// created is when the notification was created, in UNIX time.
|
||||
Created *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=created,proto3" json:"created,omitempty"`
|
||||
// unscoped is whether the notification shouldn't be restricted to a specific audience. This is to prevent the potential future possibility that a user-specific notification contains information that the user should no longer be allowed to see. Default is true.
|
||||
@@ -225,13 +223,6 @@ func (*NotificationSpec) Descriptor() ([]byte, []int) {
|
||||
return file_teleport_notifications_v1_notifications_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *NotificationSpec) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NotificationSpec) GetCreated() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.Created
|
||||
@@ -960,133 +951,132 @@ var file_teleport_notifications_v1_notifications_proto_rawDesc = []byte{
|
||||
0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x70, 0x65, 0x63,
|
||||
0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x90, 0x01, 0x0a, 0x10, 0x4e, 0x6f, 0x74, 0x69, 0x66,
|
||||
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x63,
|
||||
0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x8a, 0x01, 0x0a, 0x10, 0x4e, 0x6f, 0x74, 0x69, 0x66,
|
||||
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x12, 0x34, 0x0a, 0x07, 0x63,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x6e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x64, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x08, 0x75, 0x6e, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x64, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xde, 0x01, 0x0a, 0x12, 0x47, 0x6c,
|
||||
0x6f, 0x62, 0x61, 0x6c, 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, 0x45, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x31, 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,
|
||||
0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0xca, 0x02, 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, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x79, 0x50, 0x65, 0x72, 0x6d,
|
||||
0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0d, 0x62, 0x79, 0x50, 0x65, 0x72,
|
||||
0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3f, 0x0a, 0x08, 0x62, 0x79, 0x5f, 0x72,
|
||||
0x6f, 0x6c, 0x65, 0x73, 0x18, 0x02, 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, 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, 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,
|
||||
0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x52,
|
||||
0x02, 0x69, 0x64, 0x22, 0xde, 0x01, 0x0a, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 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, 0x45, 0x0a,
|
||||
0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 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, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04,
|
||||
0x73, 0x70, 0x65, 0x63, 0x22, 0xca, 0x02, 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,
|
||||
0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x42, 0x79, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x48, 0x00, 0x52, 0x0d, 0x62, 0x79, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x12, 0x3f, 0x0a, 0x08, 0x62, 0x79, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x02,
|
||||
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, 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, 0x44, 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, 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, 0x55, 0x73, 0x65, 0x72,
|
||||
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, 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, 0x44, 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,
|
||||
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, 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, 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, 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, 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, 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 (
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,13 +41,9 @@ const (
|
||||
NotificationService_DeleteUserNotification_FullMethodName = "/teleport.notifications.v1.NotificationService/DeleteUserNotification"
|
||||
NotificationService_CreateGlobalNotification_FullMethodName = "/teleport.notifications.v1.NotificationService/CreateGlobalNotification"
|
||||
NotificationService_DeleteGlobalNotification_FullMethodName = "/teleport.notifications.v1.NotificationService/DeleteGlobalNotification"
|
||||
NotificationService_ListUserNotifications_FullMethodName = "/teleport.notifications.v1.NotificationService/ListUserNotifications"
|
||||
NotificationService_ListNotifications_FullMethodName = "/teleport.notifications.v1.NotificationService/ListNotifications"
|
||||
NotificationService_UpsertUserNotificationState_FullMethodName = "/teleport.notifications.v1.NotificationService/UpsertUserNotificationState"
|
||||
NotificationService_DeleteUserNotificationState_FullMethodName = "/teleport.notifications.v1.NotificationService/DeleteUserNotificationState"
|
||||
NotificationService_ListUserNotificationStates_FullMethodName = "/teleport.notifications.v1.NotificationService/ListUserNotificationStates"
|
||||
NotificationService_GetUserLastSeenNotification_FullMethodName = "/teleport.notifications.v1.NotificationService/GetUserLastSeenNotification"
|
||||
NotificationService_UpsertUserLastSeenNotification_FullMethodName = "/teleport.notifications.v1.NotificationService/UpsertUserLastSeenNotification"
|
||||
NotificationService_DeleteUserLastSeenNotification_FullMethodName = "/teleport.notifications.v1.NotificationService/DeleteUserLastSeenNotification"
|
||||
)
|
||||
|
||||
// NotificationServiceClient is the client API for NotificationService service.
|
||||
@@ -62,20 +58,12 @@ type NotificationServiceClient interface {
|
||||
CreateGlobalNotification(ctx context.Context, in *CreateGlobalNotificationRequest, opts ...grpc.CallOption) (*GlobalNotification, error)
|
||||
// DeleteGlobalNotification deletes a global notification.
|
||||
DeleteGlobalNotification(ctx context.Context, in *DeleteGlobalNotificationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
// ListUserNotificationsRequest is the request for listing a user's notifications, which include user-specific ones as well as global notifications that match them.
|
||||
ListUserNotifications(ctx context.Context, in *ListUserNotificationsRequest, opts ...grpc.CallOption) (*ListUserNotificationsResponse, error)
|
||||
// ListNotifications returns a paginated list of a user's notifications.
|
||||
ListNotifications(ctx context.Context, in *ListNotificationsRequest, opts ...grpc.CallOption) (*ListNotificationsResponse, error)
|
||||
// UpsertUserNotificationState creates or updates a user notification state which records whether the user has clicked on or dismissed a notification.
|
||||
UpsertUserNotificationState(ctx context.Context, in *UpsertUserNotificationStateRequest, opts ...grpc.CallOption) (*UserNotificationState, error)
|
||||
// DeleteUserNotificationState deletes a user notification state object.
|
||||
DeleteUserNotificationState(ctx context.Context, in *DeleteUserNotificationStateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
// ListUserNotificationStates returns a page of a user's notification states.
|
||||
ListUserNotificationStates(ctx context.Context, in *ListUserNotificationStatesRequest, opts ...grpc.CallOption) (*ListUserNotificationStatesResponse, error)
|
||||
// GetUserLastSeenNotification returns a user's last seen notification item.
|
||||
GetUserLastSeenNotification(ctx context.Context, in *GetUserLastSeenNotificationRequest, opts ...grpc.CallOption) (*UserLastSeenNotification, error)
|
||||
// UpsertUserLastSeenNotification creates or updates a user's last seen notification item.
|
||||
UpsertUserLastSeenNotification(ctx context.Context, in *UpsertUserLastSeenNotificationRequest, opts ...grpc.CallOption) (*UserLastSeenNotification, error)
|
||||
// DeleteUserLastSeenNotification deletes a user's last seen notification item.
|
||||
DeleteUserLastSeenNotification(ctx context.Context, in *DeleteUserLastSeenNotificationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
}
|
||||
|
||||
type notificationServiceClient struct {
|
||||
@@ -122,9 +110,9 @@ func (c *notificationServiceClient) DeleteGlobalNotification(ctx context.Context
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *notificationServiceClient) ListUserNotifications(ctx context.Context, in *ListUserNotificationsRequest, opts ...grpc.CallOption) (*ListUserNotificationsResponse, error) {
|
||||
out := new(ListUserNotificationsResponse)
|
||||
err := c.cc.Invoke(ctx, NotificationService_ListUserNotifications_FullMethodName, in, out, opts...)
|
||||
func (c *notificationServiceClient) ListNotifications(ctx context.Context, in *ListNotificationsRequest, opts ...grpc.CallOption) (*ListNotificationsResponse, error) {
|
||||
out := new(ListNotificationsResponse)
|
||||
err := c.cc.Invoke(ctx, NotificationService_ListNotifications_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -140,33 +128,6 @@ func (c *notificationServiceClient) UpsertUserNotificationState(ctx context.Cont
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *notificationServiceClient) DeleteUserNotificationState(ctx context.Context, in *DeleteUserNotificationStateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, NotificationService_DeleteUserNotificationState_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *notificationServiceClient) ListUserNotificationStates(ctx context.Context, in *ListUserNotificationStatesRequest, opts ...grpc.CallOption) (*ListUserNotificationStatesResponse, error) {
|
||||
out := new(ListUserNotificationStatesResponse)
|
||||
err := c.cc.Invoke(ctx, NotificationService_ListUserNotificationStates_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *notificationServiceClient) GetUserLastSeenNotification(ctx context.Context, in *GetUserLastSeenNotificationRequest, opts ...grpc.CallOption) (*UserLastSeenNotification, error) {
|
||||
out := new(UserLastSeenNotification)
|
||||
err := c.cc.Invoke(ctx, NotificationService_GetUserLastSeenNotification_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *notificationServiceClient) UpsertUserLastSeenNotification(ctx context.Context, in *UpsertUserLastSeenNotificationRequest, opts ...grpc.CallOption) (*UserLastSeenNotification, error) {
|
||||
out := new(UserLastSeenNotification)
|
||||
err := c.cc.Invoke(ctx, NotificationService_UpsertUserLastSeenNotification_FullMethodName, in, out, opts...)
|
||||
@@ -176,15 +137,6 @@ func (c *notificationServiceClient) UpsertUserLastSeenNotification(ctx context.C
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *notificationServiceClient) DeleteUserLastSeenNotification(ctx context.Context, in *DeleteUserLastSeenNotificationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, NotificationService_DeleteUserLastSeenNotification_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// NotificationServiceServer is the server API for NotificationService service.
|
||||
// All implementations must embed UnimplementedNotificationServiceServer
|
||||
// for forward compatibility
|
||||
@@ -197,20 +149,12 @@ type NotificationServiceServer interface {
|
||||
CreateGlobalNotification(context.Context, *CreateGlobalNotificationRequest) (*GlobalNotification, error)
|
||||
// DeleteGlobalNotification deletes a global notification.
|
||||
DeleteGlobalNotification(context.Context, *DeleteGlobalNotificationRequest) (*emptypb.Empty, error)
|
||||
// ListUserNotificationsRequest is the request for listing a user's notifications, which include user-specific ones as well as global notifications that match them.
|
||||
ListUserNotifications(context.Context, *ListUserNotificationsRequest) (*ListUserNotificationsResponse, error)
|
||||
// ListNotifications returns a paginated list of a user's notifications.
|
||||
ListNotifications(context.Context, *ListNotificationsRequest) (*ListNotificationsResponse, error)
|
||||
// UpsertUserNotificationState creates or updates a user notification state which records whether the user has clicked on or dismissed a notification.
|
||||
UpsertUserNotificationState(context.Context, *UpsertUserNotificationStateRequest) (*UserNotificationState, error)
|
||||
// DeleteUserNotificationState deletes a user notification state object.
|
||||
DeleteUserNotificationState(context.Context, *DeleteUserNotificationStateRequest) (*emptypb.Empty, error)
|
||||
// ListUserNotificationStates returns a page of a user's notification states.
|
||||
ListUserNotificationStates(context.Context, *ListUserNotificationStatesRequest) (*ListUserNotificationStatesResponse, error)
|
||||
// GetUserLastSeenNotification returns a user's last seen notification item.
|
||||
GetUserLastSeenNotification(context.Context, *GetUserLastSeenNotificationRequest) (*UserLastSeenNotification, error)
|
||||
// UpsertUserLastSeenNotification creates or updates a user's last seen notification item.
|
||||
UpsertUserLastSeenNotification(context.Context, *UpsertUserLastSeenNotificationRequest) (*UserLastSeenNotification, error)
|
||||
// DeleteUserLastSeenNotification deletes a user's last seen notification item.
|
||||
DeleteUserLastSeenNotification(context.Context, *DeleteUserLastSeenNotificationRequest) (*emptypb.Empty, error)
|
||||
mustEmbedUnimplementedNotificationServiceServer()
|
||||
}
|
||||
|
||||
@@ -230,27 +174,15 @@ func (UnimplementedNotificationServiceServer) CreateGlobalNotification(context.C
|
||||
func (UnimplementedNotificationServiceServer) DeleteGlobalNotification(context.Context, *DeleteGlobalNotificationRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteGlobalNotification not implemented")
|
||||
}
|
||||
func (UnimplementedNotificationServiceServer) ListUserNotifications(context.Context, *ListUserNotificationsRequest) (*ListUserNotificationsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListUserNotifications not implemented")
|
||||
func (UnimplementedNotificationServiceServer) ListNotifications(context.Context, *ListNotificationsRequest) (*ListNotificationsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListNotifications not implemented")
|
||||
}
|
||||
func (UnimplementedNotificationServiceServer) UpsertUserNotificationState(context.Context, *UpsertUserNotificationStateRequest) (*UserNotificationState, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpsertUserNotificationState not implemented")
|
||||
}
|
||||
func (UnimplementedNotificationServiceServer) DeleteUserNotificationState(context.Context, *DeleteUserNotificationStateRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteUserNotificationState not implemented")
|
||||
}
|
||||
func (UnimplementedNotificationServiceServer) ListUserNotificationStates(context.Context, *ListUserNotificationStatesRequest) (*ListUserNotificationStatesResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListUserNotificationStates not implemented")
|
||||
}
|
||||
func (UnimplementedNotificationServiceServer) GetUserLastSeenNotification(context.Context, *GetUserLastSeenNotificationRequest) (*UserLastSeenNotification, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetUserLastSeenNotification not implemented")
|
||||
}
|
||||
func (UnimplementedNotificationServiceServer) UpsertUserLastSeenNotification(context.Context, *UpsertUserLastSeenNotificationRequest) (*UserLastSeenNotification, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpsertUserLastSeenNotification not implemented")
|
||||
}
|
||||
func (UnimplementedNotificationServiceServer) DeleteUserLastSeenNotification(context.Context, *DeleteUserLastSeenNotificationRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteUserLastSeenNotification not implemented")
|
||||
}
|
||||
func (UnimplementedNotificationServiceServer) mustEmbedUnimplementedNotificationServiceServer() {}
|
||||
|
||||
// UnsafeNotificationServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
@@ -336,20 +268,20 @@ func _NotificationService_DeleteGlobalNotification_Handler(srv interface{}, ctx
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _NotificationService_ListUserNotifications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListUserNotificationsRequest)
|
||||
func _NotificationService_ListNotifications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListNotificationsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(NotificationServiceServer).ListUserNotifications(ctx, in)
|
||||
return srv.(NotificationServiceServer).ListNotifications(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: NotificationService_ListUserNotifications_FullMethodName,
|
||||
FullMethod: NotificationService_ListNotifications_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(NotificationServiceServer).ListUserNotifications(ctx, req.(*ListUserNotificationsRequest))
|
||||
return srv.(NotificationServiceServer).ListNotifications(ctx, req.(*ListNotificationsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
@@ -372,60 +304,6 @@ func _NotificationService_UpsertUserNotificationState_Handler(srv interface{}, c
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _NotificationService_DeleteUserNotificationState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteUserNotificationStateRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(NotificationServiceServer).DeleteUserNotificationState(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: NotificationService_DeleteUserNotificationState_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(NotificationServiceServer).DeleteUserNotificationState(ctx, req.(*DeleteUserNotificationStateRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _NotificationService_ListUserNotificationStates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListUserNotificationStatesRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(NotificationServiceServer).ListUserNotificationStates(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: NotificationService_ListUserNotificationStates_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(NotificationServiceServer).ListUserNotificationStates(ctx, req.(*ListUserNotificationStatesRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _NotificationService_GetUserLastSeenNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetUserLastSeenNotificationRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(NotificationServiceServer).GetUserLastSeenNotification(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: NotificationService_GetUserLastSeenNotification_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(NotificationServiceServer).GetUserLastSeenNotification(ctx, req.(*GetUserLastSeenNotificationRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _NotificationService_UpsertUserLastSeenNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpsertUserLastSeenNotificationRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -444,24 +322,6 @@ func _NotificationService_UpsertUserLastSeenNotification_Handler(srv interface{}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _NotificationService_DeleteUserLastSeenNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteUserLastSeenNotificationRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(NotificationServiceServer).DeleteUserLastSeenNotification(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: NotificationService_DeleteUserLastSeenNotification_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(NotificationServiceServer).DeleteUserLastSeenNotification(ctx, req.(*DeleteUserLastSeenNotificationRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// NotificationService_ServiceDesc is the grpc.ServiceDesc for NotificationService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -486,33 +346,17 @@ var NotificationService_ServiceDesc = grpc.ServiceDesc{
|
||||
Handler: _NotificationService_DeleteGlobalNotification_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListUserNotifications",
|
||||
Handler: _NotificationService_ListUserNotifications_Handler,
|
||||
MethodName: "ListNotifications",
|
||||
Handler: _NotificationService_ListNotifications_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpsertUserNotificationState",
|
||||
Handler: _NotificationService_UpsertUserNotificationState_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteUserNotificationState",
|
||||
Handler: _NotificationService_DeleteUserNotificationState_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListUserNotificationStates",
|
||||
Handler: _NotificationService_ListUserNotificationStates_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetUserLastSeenNotification",
|
||||
Handler: _NotificationService_GetUserLastSeenNotification_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpsertUserLastSeenNotification",
|
||||
Handler: _NotificationService_UpsertUserLastSeenNotification_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteUserLastSeenNotification",
|
||||
Handler: _NotificationService_DeleteUserLastSeenNotification_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "teleport/notifications/v1/notifications_service.proto",
|
||||
|
||||
@@ -42,8 +42,8 @@ message Notification {
|
||||
|
||||
// NotificationSpec is the notification specification.
|
||||
message NotificationSpec {
|
||||
// id is the UUID of the notification, in UUIDv7 format.
|
||||
string id = 1;
|
||||
reserved 1;
|
||||
reserved "id";
|
||||
// created is when the notification was created, in UNIX time.
|
||||
google.protobuf.Timestamp created = 2;
|
||||
// unscoped is whether the notification shouldn't be restricted to a specific audience. This is to prevent the potential future possibility that a user-specific notification contains information that the user should no longer be allowed to see. Default is true.
|
||||
|
||||
@@ -21,6 +21,7 @@ syntax = "proto3";
|
||||
package teleport.notifications.v1;
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "teleport/notifications/v1/notifications.proto";
|
||||
|
||||
option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1;notificationsv1";
|
||||
@@ -37,22 +38,14 @@ service NotificationService {
|
||||
// DeleteGlobalNotification deletes a global notification.
|
||||
rpc DeleteGlobalNotification(DeleteGlobalNotificationRequest) returns (google.protobuf.Empty);
|
||||
|
||||
// ListUserNotificationsRequest is the request for listing a user's notifications, which include user-specific ones as well as global notifications that match them.
|
||||
rpc ListUserNotifications(ListUserNotificationsRequest) returns (ListUserNotificationsResponse);
|
||||
// ListNotifications returns a paginated list of a user's notifications.
|
||||
rpc ListNotifications(ListNotificationsRequest) returns (ListNotificationsResponse);
|
||||
|
||||
// UpsertUserNotificationState creates or updates a user notification state which records whether the user has clicked on or dismissed a notification.
|
||||
rpc UpsertUserNotificationState(UpsertUserNotificationStateRequest) returns (UserNotificationState);
|
||||
// DeleteUserNotificationState deletes a user notification state object.
|
||||
rpc DeleteUserNotificationState(DeleteUserNotificationStateRequest) returns (google.protobuf.Empty);
|
||||
// ListUserNotificationStates returns a page of a user's notification states.
|
||||
rpc ListUserNotificationStates(ListUserNotificationStatesRequest) returns (ListUserNotificationStatesResponse);
|
||||
|
||||
// GetUserLastSeenNotification returns a user's last seen notification item.
|
||||
rpc GetUserLastSeenNotification(GetUserLastSeenNotificationRequest) returns (UserLastSeenNotification);
|
||||
// UpsertUserLastSeenNotification creates or updates a user's last seen notification item.
|
||||
rpc UpsertUserLastSeenNotification(UpsertUserLastSeenNotificationRequest) returns (UserLastSeenNotification);
|
||||
// DeleteUserLastSeenNotification deletes a user's last seen notification item.
|
||||
rpc DeleteUserLastSeenNotification(DeleteUserLastSeenNotificationRequest) returns (google.protobuf.Empty);
|
||||
}
|
||||
|
||||
// CreateUserNotificationRequest is the request for creating a user-specific notification.
|
||||
@@ -71,22 +64,22 @@ message DeleteUserNotificationRequest {
|
||||
string notification_id = 2;
|
||||
}
|
||||
|
||||
// ListUserNotificationsRequest is the request for listing a user's notifications, which include user-specific ones as well as global notifications that match them.
|
||||
message ListUserNotificationsRequest {
|
||||
// username is the username of the user the notifications to list are for.
|
||||
string username = 1;
|
||||
// ListNotificationsRequest is the request for listing a user's notifications.
|
||||
message ListNotificationsRequest {
|
||||
// page_size is the size of the page to return.
|
||||
int32 page_size = 2;
|
||||
int32 page_size = 1;
|
||||
// page_token is the next_page_token value returned from a previous ListUserNotifications request, if any.
|
||||
string page_token = 3;
|
||||
string page_token = 2;
|
||||
}
|
||||
|
||||
// ListUserNotificationsResponse is the response from listing a user's user-specific notifications.
|
||||
message ListUserNotificationsResponse {
|
||||
// ListNotificationsResponse is the response from listing a user's notifications.
|
||||
message ListNotificationsResponse {
|
||||
// notifications is the notifications returned.
|
||||
repeated Notification notifications = 1;
|
||||
// next_page_token is the token to retrieve the next page of results, this will be empty if there are no more results.
|
||||
string next_page_token = 2;
|
||||
// next_page_token contains the next page token to use as the start key for the next page of notifications.
|
||||
string next_page_token = 3;
|
||||
// user_last_seen_notification_timestamp is the timestamp of the last notification the user has seen.
|
||||
google.protobuf.Timestamp user_last_seen_notification_timestamp = 4;
|
||||
}
|
||||
|
||||
// CreateGlobalNotificationRequest is the request for creating a global notification.
|
||||
@@ -109,52 +102,10 @@ message UpsertUserNotificationStateRequest {
|
||||
UserNotificationState user_notification_state = 2;
|
||||
}
|
||||
|
||||
// UpdateUserNotificationStateRequest is the request for updating a user notification state.
|
||||
message UpdateUserNotificationStateRequest {
|
||||
// username is the username of the user.
|
||||
string username = 1;
|
||||
// user_notification_state is the updated user notification state.
|
||||
UserNotificationState user_notification_state = 2;
|
||||
}
|
||||
|
||||
// DeleteUserNotificationState is the request for deleting a user notification state.
|
||||
message DeleteUserNotificationStateRequest {
|
||||
// username is the username of the user.
|
||||
string username = 1;
|
||||
// notification_id is the ID of the notification the notifiation state to delete is for.
|
||||
string notification_id = 2;
|
||||
}
|
||||
|
||||
// ListUserNotificationStatesRequest is the request for listing a user's notification states.
|
||||
message ListUserNotificationStatesRequest {
|
||||
// username is the username of the user.
|
||||
string username = 1;
|
||||
}
|
||||
|
||||
// ListUserNotificationStatesResponse is the response from listing a user's notification states.
|
||||
message ListUserNotificationStatesResponse {
|
||||
// notification_states is the user notification state items returned.
|
||||
repeated UserNotificationState notification_states = 1;
|
||||
// next_page_token is the token to retrieve the next page of results, this will be empty if there are no more results.
|
||||
string next_page_token = 2;
|
||||
}
|
||||
|
||||
// GetUserLastSeenNotificationRequest is the request for getting a user's last seen notification.
|
||||
message GetUserLastSeenNotificationRequest {
|
||||
// username is the username of the user.
|
||||
string username = 1;
|
||||
}
|
||||
|
||||
// UpsertUserLastSeenNotificationRequest is the request for creating or updating a user's last seen notification.
|
||||
message UpsertUserLastSeenNotificationRequest {
|
||||
// username is the username of the user.
|
||||
string username = 1;
|
||||
// user_notification_state is the udpated user last seen notification item.
|
||||
// user_notification_state is the updated user last seen notification item.
|
||||
UserLastSeenNotification user_last_seen_notification = 2;
|
||||
}
|
||||
|
||||
// DeleteUserLastSeenNotificationRequest is the request for deleting a user's last seen notification.
|
||||
message DeleteUserLastSeenNotificationRequest {
|
||||
// username is the username of the user.
|
||||
string username = 1;
|
||||
}
|
||||
|
||||
@@ -986,6 +986,15 @@ const (
|
||||
PluginGenerationLabel = TeleportInternalLabelPrefix + "plugin-generation"
|
||||
)
|
||||
|
||||
const (
|
||||
// NotificationTitleLabel is the label which contains the title of the notification.
|
||||
NotificationTitleLabel = TeleportInternalLabelPrefix + "title"
|
||||
// NotificationClickedLabel is the label which contains whether the notification has been clicked on by the user.
|
||||
NotificationClickedLabel = TeleportInternalLabelPrefix + "clicked"
|
||||
// NotificationScope is the label which contains the scope of the notification, either "user" or "global"
|
||||
NotificationScope = TeleportInternalLabelPrefix + "scope"
|
||||
)
|
||||
|
||||
const (
|
||||
// InstallMethodAWSOIDCDeployServiceEnvVar is the env var used to detect if the agent was installed
|
||||
// using the DeployService action of the AWS OIDC integration.
|
||||
|
||||
+26
-12
@@ -629,6 +629,10 @@ func (c *Client) IntegrationAWSOIDCClient() integrationv1.AWSOIDCServiceClient {
|
||||
return integrationv1.NewAWSOIDCServiceClient(c.APIClient.GetConnection())
|
||||
}
|
||||
|
||||
func (c *Client) NotificationServiceClient() notificationsv1.NotificationServiceClient {
|
||||
return notificationsv1.NewNotificationServiceClient(c.APIClient.GetConnection())
|
||||
}
|
||||
|
||||
// DiscoveryConfigClient returns a client for managing the DiscoveryConfig resource.
|
||||
func (c *Client) DiscoveryConfigClient() services.DiscoveryConfigWithStatusUpdater {
|
||||
return c.APIClient.DiscoveryConfigClient()
|
||||
@@ -654,6 +658,22 @@ func (c *Client) SetStaticTokens(st types.StaticTokens) error {
|
||||
return trace.NotImplemented(notImplementedMessage)
|
||||
}
|
||||
|
||||
// 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, username string, uns *notificationsv1.UserNotificationState) (*notificationsv1.UserNotificationState, error) {
|
||||
return c.APIClient.UpsertUserNotificationState(ctx, ¬ificationsv1.UpsertUserNotificationStateRequest{
|
||||
Username: username,
|
||||
UserNotificationState: uns,
|
||||
})
|
||||
}
|
||||
|
||||
// UpsertUserLastSeenNotification creates or updates a user's last seen notification item.
|
||||
func (c *Client) UpsertUserLastSeenNotification(ctx context.Context, username string, ulsn *notificationsv1.UserLastSeenNotification) (*notificationsv1.UserLastSeenNotification, error) {
|
||||
return c.APIClient.UpsertUserLastSeenNotification(ctx, ¬ificationsv1.UpsertUserLastSeenNotificationRequest{
|
||||
Username: username,
|
||||
UserLastSeenNotification: ulsn,
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -672,18 +692,6 @@ func (c *Client) DeleteGlobalNotification(ctx context.Context, notificationId st
|
||||
return trace.NotImplemented(notImplementedMessage)
|
||||
}
|
||||
|
||||
// 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, username string, state *notificationsv1.UserNotificationState) (*notificationsv1.UserNotificationState, error) {
|
||||
// TODO(rudream): implement client methods for notifications
|
||||
return nil, trace.NotImplemented(notImplementedMessage)
|
||||
}
|
||||
|
||||
// UpsertUserLastSeenNotification creates or updates a user's last seen notification item.
|
||||
func (c *Client) UpsertUserLastSeenNotification(ctx context.Context, username string, ulsn *notificationsv1.UserLastSeenNotification) (*notificationsv1.UserLastSeenNotification, error) {
|
||||
// TODO(rudream): implement client methods for notifications
|
||||
return nil, trace.NotImplemented(notImplementedMessage)
|
||||
}
|
||||
|
||||
// DeleteAllGlobalNotifications not implemented: can only be called locally.
|
||||
func (c *Client) DeleteAllGlobalNotifications(ctx context.Context) error {
|
||||
return trace.NotImplemented(notImplementedMessage)
|
||||
@@ -1608,6 +1616,12 @@ type ClientI interface {
|
||||
// (as per the default gRPC behavior).
|
||||
WorkloadIdentityServiceClient() machineidv1pb.WorkloadIdentityServiceClient
|
||||
|
||||
// NotificationServiceClient returns a notification service client.
|
||||
// Clients connecting to older Teleport versions, still get a client
|
||||
// when calling this method, but all RPCs will return "not implemented" errors
|
||||
// (as per the default gRPC behavior).
|
||||
NotificationServiceClient() notificationsv1.NotificationServiceClient
|
||||
|
||||
// ClusterConfigClient returns a Cluster Configuration client.
|
||||
// Clients connecting to non-Enterprise clusters, or older Teleport versions,
|
||||
// still get a client when calling this method, but all RPCs will return
|
||||
|
||||
@@ -60,6 +60,7 @@ import (
|
||||
loginrulepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1"
|
||||
machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1"
|
||||
mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1"
|
||||
notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
|
||||
oktapb "github.com/gravitational/teleport/api/gen/proto/go/teleport/okta/v1"
|
||||
presencev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1"
|
||||
trustpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1"
|
||||
@@ -83,6 +84,7 @@ import (
|
||||
kubewaitingcontainerv1 "github.com/gravitational/teleport/lib/auth/kubewaitingcontainer"
|
||||
"github.com/gravitational/teleport/lib/auth/loginrule"
|
||||
"github.com/gravitational/teleport/lib/auth/machineid/machineidv1"
|
||||
notifications "github.com/gravitational/teleport/lib/auth/notifications/notificationsv1"
|
||||
"github.com/gravitational/teleport/lib/auth/okta"
|
||||
"github.com/gravitational/teleport/lib/auth/presence/presencev1"
|
||||
"github.com/gravitational/teleport/lib/auth/trust/trustv1"
|
||||
@@ -5300,6 +5302,18 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) {
|
||||
}
|
||||
accessmonitoringrules.RegisterAccessMonitoringRulesServiceServer(server, accessMonitoringRuleServer)
|
||||
|
||||
// Initialize and register the notifications service.
|
||||
notificationsServer, err := notifications.NewService(notifications.ServiceConfig{
|
||||
Authorizer: cfg.Authorizer,
|
||||
Backend: cfg.AuthServer.Services,
|
||||
UserNotificationCache: cfg.AuthServer.UserNotificationCache,
|
||||
GlobalNotificationCache: cfg.AuthServer.GlobalNotificationCache,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
notificationsv1.RegisterNotificationServiceServer(server, notificationsServer)
|
||||
|
||||
// Only register the service if this is an open source build. Enterprise builds
|
||||
// register the actual service via an auth plugin, if we register here then all
|
||||
// Enterprise builds would fail with a duplicate service registered error.
|
||||
|
||||
@@ -298,6 +298,7 @@ func NewTestAuthServer(cfg TestAuthServerConfig) (*TestAuthServer, error) {
|
||||
SkipPeriodicOperations: true,
|
||||
Emitter: emitter,
|
||||
TraceClient: cfg.TraceClient,
|
||||
Clock: cfg.Clock,
|
||||
KeyStoreConfig: keystore.Config{
|
||||
Software: keystore.SoftwareConfig{
|
||||
RSAKeyPairSource: authority.New().GenerateKeyPair,
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
|
||||
notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
)
|
||||
|
||||
func TestNotifications(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
fakeClock := clockwork.NewFakeClock()
|
||||
|
||||
authServer, err := NewTestAuthServer(TestAuthServerConfig{
|
||||
Dir: t.TempDir(),
|
||||
Clock: fakeClock,
|
||||
CacheEnabled: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
srv, err := authServer.NewTestTLSServer()
|
||||
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, srv.Close()) })
|
||||
|
||||
roles := map[string]types.RoleSpecV6{
|
||||
// auditors have access to nodes and databases, and the "auditor" and "user" logins
|
||||
"auditors": {
|
||||
Allow: types.RoleConditions{
|
||||
Logins: []string{"auditor", "user"},
|
||||
Rules: []types.Rule{
|
||||
{
|
||||
Resources: []string{types.KindNode, types.KindDatabase},
|
||||
Verbs: services.RW(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// managers have access to review requests for the "intern" role
|
||||
"managers": {
|
||||
Allow: types.RoleConditions{
|
||||
Logins: []string{"user"},
|
||||
ReviewRequests: &types.AccessReviewConditions{
|
||||
Roles: []string{"intern"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for roleName, roleSpec := range roles {
|
||||
role, err := types.NewRole(roleName, roleSpec)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = srv.Auth().UpsertRole(ctx, role)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
auditorUsername := "auditor"
|
||||
managerUsername := "manager"
|
||||
|
||||
userRolesMap := map[string][]string{
|
||||
auditorUsername: {"auditors"},
|
||||
managerUsername: {"managers"},
|
||||
}
|
||||
|
||||
// Create the users with their roles.
|
||||
for username, roles := range userRolesMap {
|
||||
user, err := types.NewUser(username)
|
||||
require.NoError(t, err)
|
||||
user.SetRoles(roles)
|
||||
_, err = srv.Auth().UpsertUser(ctx, user)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Describes a set of mock notifications to be created.
|
||||
// The number in each notification's description represents the creation order of the notifications.
|
||||
// eg. "auditor-3" will be the third notification created for auditor, and the third from last in the list (since it's the third oldest),
|
||||
testNotifications := []struct {
|
||||
userNotification *notificationsv1.Notification
|
||||
globalNotification *notificationsv1.GlobalNotification
|
||||
}{
|
||||
{
|
||||
userNotification: newUserNotification(t, auditorUsername, "auditor-1"),
|
||||
},
|
||||
{
|
||||
userNotification: newUserNotification(t, auditorUsername, "auditor-2"),
|
||||
},
|
||||
{
|
||||
// Matcher matches by the role "auditors"
|
||||
globalNotification: newGlobalNotification(t, "auditor-3", ¬ificationsv1.GlobalNotificationSpec{
|
||||
Matcher: ¬ificationsv1.GlobalNotificationSpec_ByRoles{
|
||||
ByRoles: ¬ificationsv1.ByRoles{
|
||||
Roles: []string{userRolesMap[auditorUsername][0]},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
// Matcher matches by the role "managers"
|
||||
globalNotification: newGlobalNotification(t, "manager-1", ¬ificationsv1.GlobalNotificationSpec{
|
||||
Matcher: ¬ificationsv1.GlobalNotificationSpec_ByRoles{
|
||||
ByRoles: ¬ificationsv1.ByRoles{
|
||||
Roles: []string{userRolesMap[managerUsername][0]},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
userNotification: newUserNotification(t, auditorUsername, "auditor-4"),
|
||||
},
|
||||
{
|
||||
// Matcher matches all, both users should see this.
|
||||
globalNotification: newGlobalNotification(t, "auditor-5,manager-2", ¬ificationsv1.GlobalNotificationSpec{
|
||||
Matcher: ¬ificationsv1.GlobalNotificationSpec_All{
|
||||
All: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
userNotification: newUserNotification(t, managerUsername, "manager-3"),
|
||||
},
|
||||
{
|
||||
// Matcher matches by read & write permission on nodes.
|
||||
globalNotification: newGlobalNotification(t, "auditor-6", ¬ificationsv1.GlobalNotificationSpec{
|
||||
Matcher: ¬ificationsv1.GlobalNotificationSpec_ByPermissions{
|
||||
ByPermissions: ¬ificationsv1.ByPermissions{
|
||||
RoleConditions: []*types.RoleConditions{
|
||||
{
|
||||
Rules: []types.Rule{
|
||||
{
|
||||
Resources: []string{types.KindNode},
|
||||
Verbs: services.RW(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
// Matcher matches by the logins "auditor" and "user". Auditor has both of them, but manager only has "user", since we set MatchAllConditions to true, only auditor
|
||||
// should get this notification.
|
||||
globalNotification: newGlobalNotification(t, "auditor-7", ¬ificationsv1.GlobalNotificationSpec{
|
||||
Matcher: ¬ificationsv1.GlobalNotificationSpec_ByPermissions{
|
||||
ByPermissions: ¬ificationsv1.ByPermissions{
|
||||
RoleConditions: []*types.RoleConditions{
|
||||
{
|
||||
Logins: []string{"auditor"},
|
||||
},
|
||||
{
|
||||
Logins: []string{"user"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MatchAllConditions: true,
|
||||
}),
|
||||
},
|
||||
{
|
||||
// Matcher matches by permission to review access requests for "intern"
|
||||
globalNotification: newGlobalNotification(t, "manager-4", ¬ificationsv1.GlobalNotificationSpec{
|
||||
Matcher: ¬ificationsv1.GlobalNotificationSpec_ByPermissions{
|
||||
ByPermissions: ¬ificationsv1.ByPermissions{
|
||||
RoleConditions: []*types.RoleConditions{
|
||||
{
|
||||
ReviewRequests: &types.AccessReviewConditions{
|
||||
Roles: []string{"intern"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
userNotification: newUserNotification(t, managerUsername, "manager-5"),
|
||||
},
|
||||
{
|
||||
// Matcher matches by the logins "auditor" and "user". Both of them have "user" and MatchAllConditions is false, so both should get this notification.
|
||||
globalNotification: newGlobalNotification(t, "auditor-8,manager-6", ¬ificationsv1.GlobalNotificationSpec{
|
||||
Matcher: ¬ificationsv1.GlobalNotificationSpec_ByPermissions{
|
||||
ByPermissions: ¬ificationsv1.ByPermissions{
|
||||
RoleConditions: []*types.RoleConditions{
|
||||
{
|
||||
Logins: []string{"auditor"},
|
||||
},
|
||||
{
|
||||
Logins: []string{"user"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
notificationIdMap := map[string]string{}
|
||||
|
||||
// Create the notifications.
|
||||
for _, n := range testNotifications {
|
||||
// We add a small delay to ensure that the timestamps in the generated UUID's are all different and in the correct order.
|
||||
// This is to prevent flakiness caused by the notifications being created in such quick succession that the timestamps are the same.
|
||||
fakeClock.Advance(50 * time.Millisecond)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
// Create the notification in the backend.
|
||||
if n.globalNotification != nil {
|
||||
created, err := srv.Auth().CreateGlobalNotification(ctx, n.globalNotification)
|
||||
notificationIdMap[created.GetSpec().GetNotification().GetMetadata().GetLabels()[types.NotificationTitleLabel]] = created.GetMetadata().GetName()
|
||||
require.NoError(t, err)
|
||||
continue
|
||||
}
|
||||
created, err := srv.Auth().CreateUserNotification(ctx, n.userNotification)
|
||||
notificationIdMap[created.GetMetadata().GetLabels()[types.NotificationTitleLabel]] = created.GetMetadata().GetName()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Test fetching notifications for auditor.
|
||||
auditorClient, err := srv.NewClient(TestUser(auditorUsername))
|
||||
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"}
|
||||
|
||||
var finalOut []*notificationsv1.Notification
|
||||
|
||||
// Upsert auditor's last seen timestamp.
|
||||
lastSeenTimestamp := timestamppb.New(time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC))
|
||||
_, err = auditorClient.UpsertUserLastSeenNotification(ctx, auditorUsername, ¬ificationsv1.UserLastSeenNotification{
|
||||
Status: ¬ificationsv1.UserLastSeenNotificationStatus{
|
||||
LastSeenTime: lastSeenTimestamp,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Fetch a page of 3 notifications.
|
||||
resp, err := auditorClient.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{
|
||||
PageSize: 3,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, auditorExpectedNotifications[:3], notificationsToTitlesList(t, resp.Notifications))
|
||||
finalOut = append(finalOut, resp.Notifications...)
|
||||
|
||||
// Verify that the nextKeys are correct.
|
||||
expectedUserNotifsNextKey := notificationIdMap["auditor-4"]
|
||||
expectedGlobalNotifsNextKey := notificationIdMap["auditor-5,manager-2"]
|
||||
expectedNextKeys := fmt.Sprintf("%s,%s",
|
||||
expectedUserNotifsNextKey,
|
||||
expectedGlobalNotifsNextKey) // "<auditor-4 notification id>,<auditor-5,manager-2 notification id>"
|
||||
|
||||
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.
|
||||
// After this fetch, there should be no more global notifications for auditor, so the next page token
|
||||
// for global notifications should be "end".
|
||||
resp, err = auditorClient.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{
|
||||
PageSize: 3,
|
||||
PageToken: resp.NextPageToken,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
expectedNextKeys = fmt.Sprintf("%s,", notificationIdMap["auditor-2"])
|
||||
|
||||
require.Equal(t, expectedNextKeys, resp.NextPageToken)
|
||||
finalOut = append(finalOut, resp.Notifications...)
|
||||
|
||||
// Fetch the rest of the notifications, starting from the previously received startKeys.
|
||||
resp, err = auditorClient.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{
|
||||
PageSize: 10,
|
||||
PageToken: resp.NextPageToken,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
finalOut = append(finalOut, resp.Notifications...)
|
||||
// Verify that all the notifications are in the list and in correct order.
|
||||
require.Equal(t, auditorExpectedNotifications, notificationsToTitlesList(t, finalOut))
|
||||
// Verify that we've reached the end of both lists.
|
||||
require.Equal(t, "", resp.NextPageToken)
|
||||
|
||||
// Mark "auditor-2" and "auditor-5,manager-2" as dismissed.
|
||||
_, err = auditorClient.UpsertUserNotificationState(ctx, auditorUsername, ¬ificationsv1.UserNotificationState{
|
||||
Spec: ¬ificationsv1.UserNotificationStateSpec{
|
||||
NotificationId: notificationIdMap["auditor-2"],
|
||||
},
|
||||
Status: ¬ificationsv1.UserNotificationStateStatus{
|
||||
NotificationState: notificationsv1.NotificationState_NOTIFICATION_STATE_DISMISSED,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = auditorClient.UpsertUserNotificationState(ctx, auditorUsername, ¬ificationsv1.UserNotificationState{
|
||||
Spec: ¬ificationsv1.UserNotificationStateSpec{
|
||||
NotificationId: notificationIdMap["auditor-5,manager-2"],
|
||||
},
|
||||
Status: ¬ificationsv1.UserNotificationStateStatus{
|
||||
NotificationState: notificationsv1.NotificationState_NOTIFICATION_STATE_DISMISSED,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Fetch notifications again and verify that the dismissed notifications are not returned.
|
||||
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"}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, auditorExpectedNotifsAfterDismissal, notificationsToTitlesList(t, resp.Notifications))
|
||||
|
||||
// Test fetching notifications for manager.
|
||||
managerClient, err := srv.NewClient(TestUser(managerUsername))
|
||||
require.NoError(t, err)
|
||||
defer managerClient.Close()
|
||||
|
||||
managerExpectedNotifications := []string{"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,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, managerExpectedNotifications, notificationsToTitlesList(t, resp.Notifications))
|
||||
// Verify that we've reached the end of both lists.
|
||||
require.Equal(t, "", resp.NextPageToken)
|
||||
|
||||
// Mark "auditor-8,manager-6" as clicked.
|
||||
_, err = managerClient.UpsertUserNotificationState(ctx, managerUsername, ¬ificationsv1.UserNotificationState{
|
||||
Spec: ¬ificationsv1.UserNotificationStateSpec{
|
||||
NotificationId: notificationIdMap["auditor-8,manager-6"],
|
||||
},
|
||||
Status: ¬ificationsv1.UserNotificationStateStatus{
|
||||
NotificationState: notificationsv1.NotificationState_NOTIFICATION_STATE_CLICKED,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Fetch notifications again and expect it to have the clicked label.
|
||||
resp, err = managerClient.ListNotifications(ctx, ¬ificationsv1.ListNotificationsRequest{
|
||||
PageSize: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
clickedNotification := resp.Notifications[0] // "auditor-8,manager-6" is the first item in the list
|
||||
clickedLabelValue := clickedNotification.GetMetadata().GetLabels()[types.NotificationClickedLabel]
|
||||
require.Equal(t, "true", clickedLabelValue)
|
||||
|
||||
// Verify that manager can't upsert a notification state for auditor
|
||||
_, err = managerClient.UpsertUserNotificationState(ctx, auditorUsername, ¬ificationsv1.UserNotificationState{
|
||||
Spec: ¬ificationsv1.UserNotificationStateSpec{
|
||||
NotificationId: notificationIdMap["auditor-7"],
|
||||
},
|
||||
Status: ¬ificationsv1.UserNotificationStateStatus{
|
||||
NotificationState: notificationsv1.NotificationState_NOTIFICATION_STATE_DISMISSED,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.True(t, trace.IsAccessDenied(err), "got error %T, expected an access denied error due to manager trying to upsert a notification state for a different user", err)
|
||||
|
||||
// Verify that manager can't uspert a user last seen notification for auditor.
|
||||
_, err = managerClient.UpsertUserLastSeenNotification(ctx, auditorUsername, ¬ificationsv1.UserLastSeenNotification{
|
||||
Status: ¬ificationsv1.UserLastSeenNotificationStatus{
|
||||
LastSeenTime: lastSeenTimestamp,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.True(t, trace.IsAccessDenied(err), "got error %T, expected an access denied error due to manager trying to upsert a last seen notification timestamp for a different user", err)
|
||||
}
|
||||
|
||||
func newUserNotification(t *testing.T, username string, title string) *notificationsv1.Notification {
|
||||
t.Helper()
|
||||
|
||||
notification := notificationsv1.Notification{
|
||||
SubKind: "test-subkind",
|
||||
Spec: ¬ificationsv1.NotificationSpec{
|
||||
Username: username,
|
||||
},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Labels: map[string]string{
|
||||
types.NotificationTitleLabel: title,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return ¬ification
|
||||
}
|
||||
|
||||
func newGlobalNotification(t *testing.T, title string, spec *notificationsv1.GlobalNotificationSpec) *notificationsv1.GlobalNotification {
|
||||
t.Helper()
|
||||
|
||||
spec.Notification = ¬ificationsv1.Notification{
|
||||
SubKind: "test-subkind",
|
||||
Spec: ¬ificationsv1.NotificationSpec{},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Labels: map[string]string{
|
||||
types.NotificationTitleLabel: title,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
notification := notificationsv1.GlobalNotification{
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
return ¬ification
|
||||
}
|
||||
|
||||
// notificationsToTitlesList accepts a list of notifications notifications and returns a slice of strings containing their titles in order, this is used to compare against
|
||||
// the expected outputs.
|
||||
func notificationsToTitlesList(t *testing.T, notifications []*notificationsv1.Notification) []string {
|
||||
t.Helper()
|
||||
var descriptions []string
|
||||
|
||||
for _, notif := range notifications {
|
||||
description := notif.GetMetadata().GetLabels()[types.NotificationTitleLabel]
|
||||
descriptions = append(descriptions, description)
|
||||
}
|
||||
|
||||
return descriptions
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
// Copyright 2024 Gravitational, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/api/client"
|
||||
apidefaults "github.com/gravitational/teleport/api/defaults"
|
||||
notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
|
||||
"github.com/gravitational/teleport/api/internalutils/stream"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/authz"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
)
|
||||
|
||||
// ServiceConfig holds configuration options for the notifications gRPC service.
|
||||
type ServiceConfig struct {
|
||||
// Backend is the interface containing all methods for backend read/writes.
|
||||
Backend Backend
|
||||
|
||||
// Authorizer is the authorizer used to check access to resources.
|
||||
Authorizer authz.Authorizer
|
||||
|
||||
// UserNotificationCache is a custom cache for user-specific notifications,
|
||||
// this is to allow fetching notifications by date in descending order.
|
||||
UserNotificationCache *services.UserNotificationCache
|
||||
|
||||
// GlobalNotificationCache is a custom cache for user-specific notifications,
|
||||
// this is to allow fetching notifications by date in descending order.
|
||||
GlobalNotificationCache *services.GlobalNotificationCache
|
||||
}
|
||||
|
||||
// Backend contains the getters required for notification states and user last seen notifications,
|
||||
// as well as the methods required by the ReviewPermissionChecker.
|
||||
type Backend interface {
|
||||
services.Notifications
|
||||
// Needed by the ReviewPermissionChecker
|
||||
services.UserLoginStatesGetter
|
||||
services.UserGetter
|
||||
services.RoleGetter
|
||||
client.ListResourcesClient
|
||||
GetRoles(ctx context.Context) ([]types.Role, error)
|
||||
GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error)
|
||||
}
|
||||
|
||||
// Service implements the teleport.notifications.v1.NotificationsService RPC Service.
|
||||
type Service struct {
|
||||
notificationsv1.UnimplementedNotificationServiceServer
|
||||
|
||||
authorizer authz.Authorizer
|
||||
backend Backend
|
||||
userNotificationCache *services.UserNotificationCache
|
||||
globalNotificationCache *services.GlobalNotificationCache
|
||||
}
|
||||
|
||||
// NewService returns a new notifications gRPC service.
|
||||
func NewService(cfg ServiceConfig) (*Service, error) {
|
||||
switch {
|
||||
case cfg.Backend == nil:
|
||||
return nil, trace.BadParameter("backend is required")
|
||||
case cfg.Authorizer == nil:
|
||||
return nil, trace.BadParameter("authorizer is required")
|
||||
case cfg.UserNotificationCache == nil:
|
||||
return nil, trace.BadParameter("user notification cache is required")
|
||||
case cfg.GlobalNotificationCache == nil:
|
||||
return nil, trace.BadParameter("global notification cache is required")
|
||||
}
|
||||
|
||||
return &Service{
|
||||
authorizer: cfg.Authorizer,
|
||||
backend: cfg.Backend,
|
||||
userNotificationCache: cfg.UserNotificationCache,
|
||||
globalNotificationCache: cfg.GlobalNotificationCache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListNotifications returns a paginated list of notifications which match the user.
|
||||
func (s *Service) ListNotifications(ctx context.Context, req *notificationsv1.ListNotificationsRequest) (*notificationsv1.ListNotificationsResponse, error) {
|
||||
authCtx, err := s.authorizer.Authorize(ctx)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
username := authCtx.User.GetName()
|
||||
|
||||
// Fetch all of the user's notification states. We do this upfront to filter out dismissed notifications.
|
||||
notificationStatesMap := make(map[string]notificationsv1.NotificationState)
|
||||
for startKey := ""; ; {
|
||||
notificationStates, nextKey, err := s.backend.ListUserNotificationStates(ctx, username, apidefaults.DefaultChunkSize, startKey)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
for _, notificationState := range notificationStates {
|
||||
if notificationState.Spec != nil && notificationState.Status != nil {
|
||||
notificationStatesMap[notificationState.Spec.NotificationId] = notificationState.Status.GetNotificationState()
|
||||
}
|
||||
}
|
||||
if nextKey == "" {
|
||||
break
|
||||
}
|
||||
startKey = nextKey
|
||||
}
|
||||
|
||||
var userNotifMatchFn = func(n *notificationsv1.Notification) bool {
|
||||
// Return true if the user hasn't dismissed this notification
|
||||
return notificationStatesMap[n.GetMetadata().GetName()] != notificationsv1.NotificationState_NOTIFICATION_STATE_DISMISSED
|
||||
}
|
||||
userKey, globalKey, found := strings.Cut(req.PageToken, ",")
|
||||
if !found && req.PageToken != "" {
|
||||
return nil, trace.BadParameter("invalid page token provided")
|
||||
}
|
||||
pageSize := int(req.PageSize)
|
||||
userNotifsStream := stream.Slice[*notificationsv1.Notification](nil)
|
||||
if userKey != "" || !found {
|
||||
userNotifsStream = stream.FilterMap(
|
||||
s.userNotificationCache.StreamUserNotifications(ctx, username, userKey),
|
||||
func(n *notificationsv1.Notification) (*notificationsv1.Notification, bool) {
|
||||
if !userNotifMatchFn(n) {
|
||||
return nil, false
|
||||
}
|
||||
return n, true
|
||||
})
|
||||
}
|
||||
globalNotifsStream := stream.Slice[*notificationsv1.GlobalNotification](nil)
|
||||
if globalKey != "" || !found {
|
||||
globalNotifsStream = stream.FilterMap(
|
||||
s.globalNotificationCache.StreamGlobalNotifications(ctx, globalKey),
|
||||
func(gn *notificationsv1.GlobalNotification) (*notificationsv1.GlobalNotification, bool) {
|
||||
if !s.matchGlobalNotification(ctx, authCtx, gn, notificationStatesMap) {
|
||||
return nil, false
|
||||
}
|
||||
return gn, true
|
||||
})
|
||||
}
|
||||
notifStream := stream.MergeStreams(
|
||||
userNotifsStream,
|
||||
globalNotifsStream,
|
||||
isMoreRecent,
|
||||
func(notification *notificationsv1.Notification) *notificationsv1.Notification {
|
||||
return notification
|
||||
},
|
||||
func(globalNotification *notificationsv1.GlobalNotification) *notificationsv1.Notification {
|
||||
notification := globalNotification.GetSpec().GetNotification()
|
||||
notification.Metadata.Name = globalNotification.GetMetadata().GetName()
|
||||
return notification
|
||||
},
|
||||
)
|
||||
var notifications []*notificationsv1.Notification
|
||||
var nextGlobalKey, nextUserKey string
|
||||
for notifStream.Next() {
|
||||
item := notifStream.Item()
|
||||
if item != nil {
|
||||
notifications = append(notifications, item)
|
||||
}
|
||||
if len(notifications) == pageSize {
|
||||
// The nextKeys should represent the next unconsumed items in their respective lists.
|
||||
// If the last item in this page (ie. the current item in the stream) was a user-specific notification, then the userNotificationsNextKey should be the next item in the userNotifsStream, and
|
||||
// the globalNotificationsNextKey should be the current (and unconsumed) item in the globalNotifsStream. And vice-versa.
|
||||
if item.GetMetadata().GetLabels()[types.NotificationScope] == "user" {
|
||||
nextGlobalKey = globalNotifsStream.Item().GetMetadata().GetName()
|
||||
// Advance to the next user-specific notification.
|
||||
ok := userNotifsStream.Next()
|
||||
if ok {
|
||||
// If it exists, set it as the userNotificationsNextKey, otherwise set it to ""
|
||||
nextUserKey = userNotifsStream.Item().GetMetadata().GetName()
|
||||
} else {
|
||||
nextUserKey = ""
|
||||
}
|
||||
} else {
|
||||
nextUserKey = userNotifsStream.Item().GetMetadata().GetName()
|
||||
// Advance to the next global notification.
|
||||
ok := globalNotifsStream.Next()
|
||||
if ok {
|
||||
// If it exists, set it as the globalNotificationsNextKey.
|
||||
nextGlobalKey = globalNotifsStream.Item().GetMetadata().GetName()
|
||||
} else {
|
||||
nextGlobalKey = ""
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
userLastSeenNotification, err := s.backend.GetUserLastSeenNotification(ctx, username)
|
||||
if err != nil && !trace.IsNotFound(err) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
// Add label to indicate notifications that the user has clicked.
|
||||
for _, notification := range notifications {
|
||||
if (notificationStatesMap[notification.GetMetadata().GetName()]) == notificationsv1.NotificationState_NOTIFICATION_STATE_CLICKED {
|
||||
notification.GetMetadata().Labels[types.NotificationClickedLabel] = "true"
|
||||
}
|
||||
}
|
||||
var nextPageToken string
|
||||
if nextUserKey != "" || nextGlobalKey != "" {
|
||||
nextPageToken = fmt.Sprintf("%s,%s", nextUserKey, nextGlobalKey)
|
||||
}
|
||||
response := ¬ificationsv1.ListNotificationsResponse{
|
||||
Notifications: notifications,
|
||||
NextPageToken: nextPageToken,
|
||||
UserLastSeenNotificationTimestamp: userLastSeenNotification.GetStatus().GetLastSeenTime(),
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *Service) matchGlobalNotification(ctx context.Context, authCtx *authz.Context, gn *notificationsv1.GlobalNotification, notificationStatesMap map[string]notificationsv1.NotificationState) bool {
|
||||
// If the user has dismissed this notification, return false.
|
||||
if notificationStatesMap[gn.GetMetadata().GetName()] == notificationsv1.NotificationState_NOTIFICATION_STATE_DISMISSED {
|
||||
return false
|
||||
}
|
||||
|
||||
switch matcher := gn.Spec.Matcher.(type) {
|
||||
case *notificationsv1.GlobalNotificationSpec_All:
|
||||
// Always return true if the matcher is "all."
|
||||
return true
|
||||
|
||||
case *notificationsv1.GlobalNotificationSpec_ByRoles:
|
||||
matcherRoles := matcher.ByRoles.GetRoles()
|
||||
userRoles := authCtx.User.GetRoles()
|
||||
|
||||
// If MatchAllConditions is true, then userRoles must contain every role in matcherRoles.
|
||||
if gn.Spec.MatchAllConditions {
|
||||
for _, matcherRole := range matcherRoles {
|
||||
// Return false if there is any role missing.
|
||||
if !slices.Contains(userRoles, matcherRole) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Return true if it matches at least one matcherRole.
|
||||
for _, matcherRole := range matcherRoles {
|
||||
if slices.Contains(userRoles, matcherRole) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
case *notificationsv1.GlobalNotificationSpec_ByPermissions:
|
||||
roleConditionsList := matcher.ByPermissions.GetRoleConditions()
|
||||
|
||||
var results []bool
|
||||
for _, roleConditions := range roleConditionsList {
|
||||
match, err := s.matchRoleConditions(ctx, authCtx, roleConditions)
|
||||
if err != nil {
|
||||
slog.WarnContext(ctx, "Encountered error while matching RoleConditions", "role_conditions", roleConditions, "error", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// If MatchAllConditions is false, we can exit at the first match.
|
||||
if !gn.Spec.MatchAllConditions && match {
|
||||
return true
|
||||
}
|
||||
|
||||
// If MatchAllConditions is true, we exit at the first non-match.
|
||||
if gn.Spec.MatchAllConditions && !match {
|
||||
return false
|
||||
}
|
||||
|
||||
results = append(results, match)
|
||||
}
|
||||
|
||||
// Return false if any of the roleConditions didn't match.
|
||||
if gn.Spec.MatchAllConditions {
|
||||
return !slices.Contains(results, false)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Service) matchRoleConditions(ctx context.Context, authCtx *authz.Context, rc *types.RoleConditions) (bool, error) {
|
||||
if len(rc.Logins) > 0 {
|
||||
userLogins := authCtx.Checker.GetAllLogins()
|
||||
var matchedLogin bool
|
||||
for _, login := range rc.Logins {
|
||||
// If at least one of the logins match, this is a match.
|
||||
if slices.Contains(userLogins, login) {
|
||||
matchedLogin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matchedLogin {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(rc.Rules) > 0 {
|
||||
var matchedRule bool
|
||||
for _, rule := range rc.Rules {
|
||||
hasAccess, err := checkAccessToRule(authCtx, rule)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(err, "encountered unexpected error when checking access to rule")
|
||||
}
|
||||
|
||||
// If the user has permissions for at least one of the rules, this is a match.
|
||||
if hasAccess {
|
||||
matchedRule = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matchedRule {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if rc.ReviewRequests != nil {
|
||||
identity := authCtx.Identity.GetIdentity()
|
||||
checker, err := services.NewReviewPermissionChecker(
|
||||
ctx,
|
||||
s.backend,
|
||||
authCtx.User.GetName(),
|
||||
&identity,
|
||||
)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// unless the user has allow directives for reviewing, they will never be able to
|
||||
// see any requests other than their own.
|
||||
if !checker.HasAllowDirectives() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// We instantiate a fake access request with the defined roles, this allows us to use our existing AccessReviewChecker to check if the
|
||||
// user is allowed to review requests for them.
|
||||
fakeAccessRequest, err := types.NewAccessRequest("fake", "fake", rc.ReviewRequests.Roles...)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(err)
|
||||
}
|
||||
|
||||
canReview, err := checker.CanReviewRequest(fakeAccessRequest)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(err, "failed to evaluate request review permissions")
|
||||
}
|
||||
|
||||
if !canReview {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// This RoleConditions object matches if there were no failed matches that returned prior to this.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// checkAccessToRule returns true if the user has the permissions defined in a rule.
|
||||
func checkAccessToRule(authCtx *authz.Context, rule types.Rule) (bool, error) {
|
||||
for _, resourceKind := range rule.Resources {
|
||||
for _, verb := range rule.Verbs {
|
||||
if err := authCtx.CheckAccessToKind(resourceKind, verb); err != nil {
|
||||
// If the user doesn't have access for the verbs for any one of the resources in the rule, then just return false.
|
||||
if trace.IsAccessDenied(err) {
|
||||
return false, nil
|
||||
// If the error is due to something else, then return it.
|
||||
} else {
|
||||
return false, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// isMoreRecent returns true if the userNotification is more recent than the globalNotification.
|
||||
func isMoreRecent(userNotification *notificationsv1.Notification, globalNotification *notificationsv1.GlobalNotification) bool {
|
||||
userNotificationTime := userNotification.GetSpec().GetCreated().AsTime()
|
||||
globalNotificationTime := globalNotification.GetSpec().GetNotification().GetSpec().GetCreated().AsTime()
|
||||
|
||||
return userNotificationTime.After(globalNotificationTime)
|
||||
}
|
||||
|
||||
// UpsertUserNotificationState creates or updates a user notification state which records whether the user has clicked on or dismissed a notification.
|
||||
func (s *Service) UpsertUserNotificationState(ctx context.Context, req *notificationsv1.UpsertUserNotificationStateRequest) (*notificationsv1.UserNotificationState, error) {
|
||||
if req.Username == "" {
|
||||
return nil, trace.BadParameter("missing username")
|
||||
}
|
||||
if req.UserNotificationState == nil {
|
||||
return nil, trace.BadParameter("missing notification state")
|
||||
}
|
||||
|
||||
authCtx, err := s.authorizer.Authorize(ctx)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
username := authCtx.User.GetName()
|
||||
if username != req.Username {
|
||||
return nil, trace.AccessDenied("a user may only update their own notification state")
|
||||
}
|
||||
|
||||
out, err := s.backend.UpsertUserNotificationState(ctx, req.Username, req.UserNotificationState)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// UpsertUserLastSeenNotification creates or updates a user's last seen notification timestamp.
|
||||
func (s *Service) UpsertUserLastSeenNotification(ctx context.Context, req *notificationsv1.UpsertUserLastSeenNotificationRequest) (*notificationsv1.UserLastSeenNotification, error) {
|
||||
if req.Username == "" {
|
||||
return nil, trace.BadParameter("missing username")
|
||||
}
|
||||
if req.UserLastSeenNotification == nil {
|
||||
return nil, trace.BadParameter("missing user last seen notification")
|
||||
}
|
||||
|
||||
authCtx, err := s.authorizer.Authorize(ctx)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
username := authCtx.User.GetName()
|
||||
if username != req.Username {
|
||||
return nil, trace.AccessDenied("a user may only update their own last seen notification timestamp")
|
||||
}
|
||||
|
||||
out, err := s.backend.UpsertUserLastSeenNotification(ctx, req.Username, req.UserLastSeenNotification)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
Vendored
+3
-3
@@ -3626,14 +3626,14 @@ func newUserNotification(t *testing.T, name string) *notificationsv1.Notificatio
|
||||
Username: name,
|
||||
},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Labels: map[string]string{"description": "test-description"},
|
||||
Labels: map[string]string{types.NotificationTitleLabel: "test-title"},
|
||||
},
|
||||
}
|
||||
|
||||
return notification
|
||||
}
|
||||
|
||||
func newGlobalNotification(t *testing.T, description string) *notificationsv1.GlobalNotification {
|
||||
func newGlobalNotification(t *testing.T, title string) *notificationsv1.GlobalNotification {
|
||||
t.Helper()
|
||||
|
||||
notification := ¬ificationsv1.GlobalNotification{
|
||||
@@ -3645,7 +3645,7 @@ func newGlobalNotification(t *testing.T, description string) *notificationsv1.Gl
|
||||
SubKind: "test-subkind",
|
||||
Spec: ¬ificationsv1.NotificationSpec{},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Labels: map[string]string{"description": description},
|
||||
Labels: map[string]string{types.NotificationTitleLabel: title},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Vendored
+2
-2
@@ -3044,7 +3044,7 @@ func (userNotificationExecutor) delete(ctx context.Context, cache *Cache, resour
|
||||
}
|
||||
|
||||
username := notification.GetSpec().GetUsername()
|
||||
notificationId := notification.GetSpec().GetId()
|
||||
notificationId := notification.GetMetadata().GetName()
|
||||
|
||||
err := cache.notificationsCache.DeleteUserNotification(ctx, username, notificationId)
|
||||
return trace.Wrap(err)
|
||||
@@ -3107,7 +3107,7 @@ func (globalNotificationExecutor) delete(ctx context.Context, cache *Cache, reso
|
||||
return trace.BadParameter("unknown Notification type, expected *notificationsv1.GlobalNotification, got %T", resource)
|
||||
}
|
||||
|
||||
notificationId := globalNotification.GetSpec().GetNotification().GetSpec().GetId()
|
||||
notificationId := globalNotification.GetMetadata().GetName()
|
||||
|
||||
err := cache.notificationsCache.DeleteGlobalNotification(ctx, notificationId)
|
||||
return trace.Wrap(err)
|
||||
|
||||
@@ -2049,7 +2049,6 @@ func (p *userNotificationParser) parse(event backend.Event) (types.Resource, err
|
||||
Version: types.V1,
|
||||
Spec: ¬ificationsv1.NotificationSpec{
|
||||
Username: parts[2],
|
||||
Id: parts[3],
|
||||
},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Name: parts[3],
|
||||
@@ -2100,9 +2099,7 @@ func (p *globalNotificationParser) parse(event backend.Event) (types.Resource, e
|
||||
Version: types.V1,
|
||||
Spec: ¬ificationsv1.GlobalNotificationSpec{
|
||||
Notification: ¬ificationsv1.Notification{
|
||||
Spec: ¬ificationsv1.NotificationSpec{
|
||||
Id: parts[2],
|
||||
},
|
||||
Spec: ¬ificationsv1.NotificationSpec{},
|
||||
},
|
||||
},
|
||||
Metadata: &headerv1.Metadata{
|
||||
|
||||
@@ -140,10 +140,7 @@ func (s *NotificationsService) CreateUserNotification(ctx context.Context, notif
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
notification.Spec.Id = uuid.String()
|
||||
|
||||
// We set this to the UUID because the service adapter uses `getName()` to determine the backend key to use when storing the notification.
|
||||
notification.Metadata.Name = notification.Spec.Id
|
||||
notification.Metadata.Name = uuid.String()
|
||||
|
||||
if err := CheckAndSetExpiry(notification, s.clock); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
@@ -151,6 +148,8 @@ func (s *NotificationsService) CreateUserNotification(ctx context.Context, notif
|
||||
|
||||
notification.Spec.Created = timestamppb.New(s.clock.Now())
|
||||
|
||||
notification.Metadata.Labels[types.NotificationScope] = "user"
|
||||
|
||||
// Append username prefix.
|
||||
serviceWithPrefix := s.userNotificationService.WithPrefix(notification.Spec.Username)
|
||||
|
||||
@@ -165,7 +164,7 @@ func (s *NotificationsService) UpsertUserNotification(ctx context.Context, notif
|
||||
}
|
||||
|
||||
// Precautionary check in case of accidental misuse.
|
||||
if notification.Spec.Id == "" {
|
||||
if notification.Metadata.Name == "" {
|
||||
return nil, trace.BadParameter("notification id is missing. Did you mean to use CreateUserNotification?")
|
||||
}
|
||||
|
||||
@@ -230,10 +229,8 @@ func (s *NotificationsService) CreateGlobalNotification(ctx context.Context, glo
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
globalNotification.Spec.Notification.Spec.Id = uuid.String()
|
||||
|
||||
// We set this to the UUID because the service adapter uses `getName()` to determine the backend key to use when storing the notification.
|
||||
globalNotification.Metadata = &headerv1.Metadata{Name: globalNotification.Spec.Notification.Spec.Id}
|
||||
globalNotification.Metadata = &headerv1.Metadata{Name: uuid.String()}
|
||||
globalNotification.Spec.Notification.Metadata.Name = uuid.String()
|
||||
|
||||
if err := CheckAndSetExpiry(globalNotification.Spec.Notification, s.clock); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
@@ -241,6 +238,8 @@ func (s *NotificationsService) CreateGlobalNotification(ctx context.Context, glo
|
||||
|
||||
globalNotification.Spec.Notification.Spec.Created = timestamppb.New(s.clock.Now())
|
||||
|
||||
globalNotification.Spec.Notification.Metadata.Labels[types.NotificationScope] = "global"
|
||||
|
||||
created, err := s.globalNotificationService.CreateResource(ctx, globalNotification)
|
||||
return created, trace.Wrap(err)
|
||||
}
|
||||
@@ -252,7 +251,7 @@ func (s *NotificationsService) UpsertGlobalNotification(ctx context.Context, glo
|
||||
}
|
||||
|
||||
// Precautionary check in case of accidental misuse.
|
||||
if globalNotification.Spec.Notification.Spec.Id == "" {
|
||||
if globalNotification.Spec.Notification.Metadata.Name == "" {
|
||||
return nil, trace.BadParameter("notification id is missing. Did you mean to use CreateGlobalNotification?")
|
||||
}
|
||||
|
||||
@@ -274,8 +273,21 @@ func (s *NotificationsService) UpsertUserNotificationState(ctx context.Context,
|
||||
|
||||
// Verify that the notification this state is for exists.
|
||||
notifServiceWithPrefix := s.userNotificationService.WithPrefix(username)
|
||||
if _, err := notifServiceWithPrefix.GetResource(ctx, state.Spec.NotificationId); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
_, err := notifServiceWithPrefix.GetResource(ctx, state.Spec.NotificationId)
|
||||
if err != nil {
|
||||
if !trace.IsNotFound(err) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// If we didn't find a user-specific notification with this ID, try finding a global notification.
|
||||
_, err := s.globalNotificationService.GetResource(ctx, state.Spec.NotificationId)
|
||||
if err != nil {
|
||||
if !trace.IsNotFound(err) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil, trace.NotFound("notification '%s' does not exist", state.Spec.NotificationId)
|
||||
}
|
||||
}
|
||||
|
||||
state.Kind = types.KindUserNotificationState
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
|
||||
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
|
||||
notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/backend/memory"
|
||||
)
|
||||
@@ -60,14 +61,14 @@ func TestUserNotificationCRUD(t *testing.T) {
|
||||
notification, err := service.CreateUserNotification(ctx, userNotification1)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, cmp.Diff(userNotification1, notification, protocmp.Transform()))
|
||||
notification1Id := notification.Spec.Id
|
||||
notification1Id := notification.GetMetadata().GetName()
|
||||
// Prevent flakiness caused by notifications being created too close one after the other, which causes their UUID timestamps to be the same
|
||||
// and the lexicographical ordering to possibly be wrong as it then relies on the random section of the UUID.
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
notification, err = service.CreateUserNotification(ctx, userNotification2)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, cmp.Diff(userNotification2, notification, protocmp.Transform()))
|
||||
notification2Id := notification.Spec.Id
|
||||
notification2Id := notification.GetMetadata().GetName()
|
||||
|
||||
// Test deleting a notification.
|
||||
err = service.DeleteUserNotification(ctx, testUsername, notification1Id)
|
||||
@@ -87,7 +88,7 @@ func TestUserNotificationCRUD(t *testing.T) {
|
||||
userNotification1 = newUserNotification(t, testUsername, "test-notification-1")
|
||||
_, err = service.CreateUserNotification(ctx, userNotification1)
|
||||
require.NoError(t, err)
|
||||
notification1Id = notification.Spec.Id
|
||||
notification1Id = notification.GetMetadata().GetName()
|
||||
err = service.DeleteAllUserNotificationsForUser(ctx, testUsername)
|
||||
require.NoError(t, err)
|
||||
// Verify that the notifications don't exist anymore by attempting to delete them.
|
||||
@@ -148,7 +149,7 @@ func TestGlobalNotificationCRUD(t *testing.T) {
|
||||
notification, err := service.CreateGlobalNotification(ctx, globalNotification1)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, cmp.Diff(globalNotification1, notification, protocmp.Transform()))
|
||||
globalNotification1Id := notification.Spec.Notification.Spec.Id
|
||||
globalNotification1Id := notification.GetMetadata().GetName()
|
||||
notification, err = service.CreateGlobalNotification(ctx, globalNotification2)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, cmp.Diff(globalNotification2, notification, protocmp.Transform()))
|
||||
@@ -188,13 +189,13 @@ func TestUserNotificationStateCRUD(t *testing.T) {
|
||||
userNotification2 := newUserNotification(t, testUsername, "test-notification-2")
|
||||
notification, err := service.CreateUserNotification(ctx, userNotification1)
|
||||
require.NoError(t, err)
|
||||
notification1Id := notification.Spec.Id
|
||||
notification1Id := notification.GetMetadata().GetName()
|
||||
// Prevent flakiness caused by notifications being created too close one after the other, which causes their UUID timestamps to be the same
|
||||
// and the lexicographical ordering to possibly be wrong as it then relies on the random section of the UUID.
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
notification, err = service.CreateUserNotification(ctx, userNotification2)
|
||||
require.NoError(t, err)
|
||||
notification2Id := notification.Spec.Id
|
||||
notification2Id := notification.GetMetadata().GetName()
|
||||
|
||||
userNotificationState1 := ¬ificationsv1.UserNotificationState{
|
||||
Spec: ¬ificationsv1.UserNotificationStateSpec{
|
||||
@@ -374,7 +375,7 @@ func TestUserLastSeenNotificationCRUD(t *testing.T) {
|
||||
require.True(t, trace.IsNotFound(err), "got error %T, expected a not found error due to user_last_seen_notification for test-username not existing", err)
|
||||
}
|
||||
|
||||
func newUserNotification(t *testing.T, username string, description string) *notificationsv1.Notification {
|
||||
func newUserNotification(t *testing.T, username string, title string) *notificationsv1.Notification {
|
||||
t.Helper()
|
||||
|
||||
notification := notificationsv1.Notification{
|
||||
@@ -383,14 +384,16 @@ func newUserNotification(t *testing.T, username string, description string) *not
|
||||
Username: username,
|
||||
},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Labels: map[string]string{"description": description},
|
||||
Labels: map[string]string{
|
||||
types.NotificationTitleLabel: title,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return ¬ification
|
||||
}
|
||||
|
||||
func newGlobalNotification(t *testing.T, description string) *notificationsv1.GlobalNotification {
|
||||
func newGlobalNotification(t *testing.T, title string) *notificationsv1.GlobalNotification {
|
||||
t.Helper()
|
||||
|
||||
notification := notificationsv1.GlobalNotification{
|
||||
@@ -402,7 +405,9 @@ func newGlobalNotification(t *testing.T, description string) *notificationsv1.Gl
|
||||
SubKind: "test-subkind",
|
||||
Spec: ¬ificationsv1.NotificationSpec{},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Labels: map[string]string{"description": description},
|
||||
Labels: map[string]string{
|
||||
types.NotificationTitleLabel: title,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
)
|
||||
|
||||
// Notifications defines an interface for managing notifications.
|
||||
@@ -62,8 +63,8 @@ func ValidateNotification(notification *notificationsv1.Notification) error {
|
||||
return trace.BadParameter("notification metadata is missing")
|
||||
}
|
||||
|
||||
if notification.Metadata.Labels == nil {
|
||||
return trace.BadParameter("notification metadata labels are missing")
|
||||
if _, exists := notification.Metadata.GetLabels()[types.NotificationTitleLabel]; !exists {
|
||||
return trace.BadParameter("notification title label is missing")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -205,6 +205,9 @@ func (c *UserNotificationCache) StreamUserNotifications(ctx context.Context, use
|
||||
// Get the initial startKey if it wasn't provided.
|
||||
if startKey == "" {
|
||||
startKey = sortcache.NextKey(endKey)
|
||||
} else {
|
||||
// The sortcache expects the key to be in <username>/<uuid> format, so we prepend the username since the startKey passed into this function will just be a UUID.
|
||||
startKey = fmt.Sprintf("%s/%s", username, startKey)
|
||||
}
|
||||
|
||||
var done bool
|
||||
@@ -266,7 +269,7 @@ func (c *UserNotificationCache) fetch(ctx context.Context) (*sortcache.SortCache
|
||||
// GetUserSpecificKey returns the key for a user-specific notification in <username>/<notification uuid> format.
|
||||
func GetUserSpecificKey(n *notificationsv1.Notification) string {
|
||||
username := n.GetSpec().GetUsername()
|
||||
id := n.GetSpec().GetId()
|
||||
id := n.GetMetadata().GetName()
|
||||
|
||||
return fmt.Sprintf("%s/%s", username, id)
|
||||
}
|
||||
|
||||
@@ -109,50 +109,50 @@ func TestUserNotificationsCache(t *testing.T) {
|
||||
usernameB := "bob"
|
||||
|
||||
// Describe a set of mock user notifications to be created.
|
||||
// The number in the description represents the order in which they are created, eg. alice-1 is the first (and thus oldest) notification to be created for alice.
|
||||
// The number in the title represents the order in which they are created, eg. alice-1 is the first (and thus oldest) notification to be created for alice.
|
||||
testNotifications := []struct {
|
||||
username string
|
||||
description string
|
||||
username string
|
||||
title string
|
||||
}{
|
||||
{
|
||||
username: usernameB,
|
||||
description: "bob-1",
|
||||
username: usernameB,
|
||||
title: "bob-1",
|
||||
},
|
||||
{
|
||||
username: usernameB,
|
||||
description: "bob-2",
|
||||
username: usernameB,
|
||||
title: "bob-2",
|
||||
},
|
||||
{
|
||||
username: usernameA,
|
||||
description: "alice-1",
|
||||
username: usernameA,
|
||||
title: "alice-1",
|
||||
},
|
||||
{
|
||||
username: usernameB,
|
||||
description: "bob-3",
|
||||
username: usernameB,
|
||||
title: "bob-3",
|
||||
},
|
||||
{
|
||||
username: usernameB,
|
||||
description: "bob-4",
|
||||
username: usernameB,
|
||||
title: "bob-4",
|
||||
},
|
||||
{
|
||||
username: usernameA,
|
||||
description: "alice-2",
|
||||
username: usernameA,
|
||||
title: "alice-2",
|
||||
},
|
||||
{
|
||||
username: usernameA,
|
||||
description: "alice-3",
|
||||
username: usernameA,
|
||||
title: "alice-3",
|
||||
},
|
||||
{
|
||||
username: usernameA,
|
||||
description: "alice-4",
|
||||
username: usernameA,
|
||||
title: "alice-4",
|
||||
},
|
||||
{
|
||||
username: usernameB,
|
||||
description: "bob-5",
|
||||
username: usernameB,
|
||||
title: "bob-5",
|
||||
},
|
||||
{
|
||||
username: usernameA,
|
||||
description: "alice-5",
|
||||
username: usernameA,
|
||||
title: "alice-5",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func TestUserNotificationsCache(t *testing.T) {
|
||||
for _, n := range testNotifications {
|
||||
// We add a delay to ensure that the timestamps in the generated UUID's are all different and in the correct order.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
notification := newUserNotification(t, n.username, n.description)
|
||||
notification := newUserNotification(t, n.username, n.title)
|
||||
// Create the notification in the backend.
|
||||
created, err := svcs.CreateUserNotification(ctx, notification)
|
||||
require.NoError(t, err)
|
||||
@@ -205,41 +205,41 @@ func TestUserNotificationsCache(t *testing.T) {
|
||||
streamA.Next()
|
||||
streamA.Next()
|
||||
streamA.Next()
|
||||
usernameAThirdItemStartKey = services.GetUserSpecificKey(streamA.Item())
|
||||
usernameAThirdItemStartKey = streamA.Item().GetMetadata().GetName()
|
||||
|
||||
var usernameBThirdItemStartKey string
|
||||
streamB := cache.StreamUserNotifications(ctx, usernameB, "")
|
||||
streamB.Next()
|
||||
streamB.Next()
|
||||
streamB.Next()
|
||||
usernameBThirdItemStartKey = services.GetUserSpecificKey(streamB.Item())
|
||||
usernameBThirdItemStartKey = streamB.Item().GetMetadata().GetName()
|
||||
|
||||
testCases := []struct {
|
||||
testName string
|
||||
username string
|
||||
startKey string
|
||||
expectedNotificationDescriptions []string
|
||||
testName string
|
||||
username string
|
||||
startKey string
|
||||
expectedNotificationTitles []string
|
||||
}{
|
||||
{
|
||||
testName: "correctly fetches sorted notifications for usernameA with no startKey",
|
||||
username: usernameA,
|
||||
// Since alice-1 is the oldest, it should be at the end of the list.
|
||||
expectedNotificationDescriptions: []string{"alice-5", "alice-4", "alice-3", "alice-2", "alice-1"},
|
||||
expectedNotificationTitles: []string{"alice-5", "alice-4", "alice-3", "alice-2", "alice-1"},
|
||||
},
|
||||
{
|
||||
testName: "correctly fetches sorted notifications for usernameB with no startKey",
|
||||
username: usernameB,
|
||||
expectedNotificationDescriptions: []string{"bob-5", "bob-4", "bob-3", "bob-2", "bob-1"},
|
||||
testName: "correctly fetches sorted notifications for usernameB with no startKey",
|
||||
username: usernameB,
|
||||
expectedNotificationTitles: []string{"bob-5", "bob-4", "bob-3", "bob-2", "bob-1"},
|
||||
}, {
|
||||
testName: "correctly fetches sorted notifications for usernameA with a startKey",
|
||||
username: usernameA,
|
||||
startKey: usernameAThirdItemStartKey,
|
||||
expectedNotificationDescriptions: []string{"alice-3", "alice-2", "alice-1"},
|
||||
testName: "correctly fetches sorted notifications for usernameA with a startKey",
|
||||
username: usernameA,
|
||||
startKey: usernameAThirdItemStartKey,
|
||||
expectedNotificationTitles: []string{"alice-3", "alice-2", "alice-1"},
|
||||
}, {
|
||||
testName: "correctly fetches sorted notifications for usernameB with a startKey",
|
||||
username: usernameB,
|
||||
startKey: usernameBThirdItemStartKey,
|
||||
expectedNotificationDescriptions: []string{"bob-3", "bob-2", "bob-1"},
|
||||
testName: "correctly fetches sorted notifications for usernameB with a startKey",
|
||||
username: usernameB,
|
||||
startKey: usernameBThirdItemStartKey,
|
||||
expectedNotificationTitles: []string{"bob-3", "bob-2", "bob-1"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -248,11 +248,11 @@ func TestUserNotificationsCache(t *testing.T) {
|
||||
var out []string
|
||||
notifsStream := cache.StreamUserNotifications(ctx, tc.username, tc.startKey)
|
||||
for notifsStream.Next() {
|
||||
desc := notifsStream.Item().GetMetadata().GetLabels()["description"]
|
||||
out = append(out, desc)
|
||||
title := notifsStream.Item().GetMetadata().GetLabels()[types.NotificationTitleLabel]
|
||||
out = append(out, title)
|
||||
}
|
||||
notifsStream.Done()
|
||||
require.Equal(t, tc.expectedNotificationDescriptions, out)
|
||||
require.Equal(t, tc.expectedNotificationTitles, out)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -303,8 +303,8 @@ func TestUserNotificationsCache(t *testing.T) {
|
||||
expected := []string{"alice-3", "alice-2", "alice-1"}
|
||||
notifsStream := cache.StreamUserNotifications(ctx, usernameA, "")
|
||||
for notifsStream.Next() {
|
||||
desc := notifsStream.Item().GetMetadata().GetLabels()["description"]
|
||||
out = append(out, desc)
|
||||
title := notifsStream.Item().GetMetadata().GetLabels()[types.NotificationTitleLabel]
|
||||
out = append(out, title)
|
||||
}
|
||||
notifsStream.Done()
|
||||
require.Equal(t, expected, out)
|
||||
@@ -320,7 +320,7 @@ func TestGlobalNotificationsCache(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
testNotificationDescriptions := []string{
|
||||
testNotificationTitles := []string{
|
||||
"gn-1",
|
||||
"gn-2",
|
||||
"gn-3",
|
||||
@@ -332,7 +332,7 @@ func TestGlobalNotificationsCache(t *testing.T) {
|
||||
var notificationIds []string
|
||||
|
||||
// Create the notifications, with a 50ms delay between each one.
|
||||
for _, gn := range testNotificationDescriptions {
|
||||
for _, gn := range testNotificationTitles {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
notification := newGlobalNotification(t, gn)
|
||||
// Create the notification in the backend.
|
||||
@@ -350,7 +350,7 @@ func TestGlobalNotificationsCache(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
gnStream.Done()
|
||||
|
||||
if len(collectedStream) == len(testNotificationDescriptions) {
|
||||
if len(collectedStream) == len(testNotificationTitles) {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -375,8 +375,8 @@ func TestGlobalNotificationsCache(t *testing.T) {
|
||||
expected := []string{"gn-5", "gn-4", "gn-3", "gn-2", "gn-1"}
|
||||
gnStream = cache.StreamGlobalNotifications(ctx, "")
|
||||
for gnStream.Next() {
|
||||
desc := gnStream.Item().GetSpec().GetNotification().GetMetadata().GetLabels()["description"]
|
||||
out = append(out, desc)
|
||||
title := gnStream.Item().GetSpec().GetNotification().GetMetadata().GetLabels()[types.NotificationTitleLabel]
|
||||
out = append(out, title)
|
||||
}
|
||||
require.Equal(t, expected, out)
|
||||
gnStream.Done()
|
||||
@@ -386,8 +386,8 @@ func TestGlobalNotificationsCache(t *testing.T) {
|
||||
expected = []string{"gn-3", "gn-2", "gn-1"}
|
||||
gnStream = cache.StreamGlobalNotifications(ctx, thirdItemStartKey)
|
||||
for gnStream.Next() {
|
||||
desc := gnStream.Item().GetSpec().GetNotification().GetMetadata().GetLabels()["description"]
|
||||
out = append(out, desc)
|
||||
title := gnStream.Item().GetSpec().GetNotification().GetMetadata().GetLabels()[types.NotificationTitleLabel]
|
||||
out = append(out, title)
|
||||
}
|
||||
require.Equal(t, expected, out)
|
||||
gnStream.Done()
|
||||
@@ -420,7 +420,7 @@ func TestGlobalNotificationsCache(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newUserNotification(t *testing.T, username string, description string) *notificationsv1.Notification {
|
||||
func newUserNotification(t *testing.T, username string, title string) *notificationsv1.Notification {
|
||||
t.Helper()
|
||||
|
||||
notification := notificationsv1.Notification{
|
||||
@@ -429,14 +429,16 @@ func newUserNotification(t *testing.T, username string, description string) *not
|
||||
Username: username,
|
||||
},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Labels: map[string]string{"description": description},
|
||||
Labels: map[string]string{
|
||||
types.NotificationTitleLabel: title,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return ¬ification
|
||||
}
|
||||
|
||||
func newGlobalNotification(t *testing.T, description string) *notificationsv1.GlobalNotification {
|
||||
func newGlobalNotification(t *testing.T, title string) *notificationsv1.GlobalNotification {
|
||||
t.Helper()
|
||||
|
||||
notification := notificationsv1.GlobalNotification{
|
||||
@@ -448,7 +450,9 @@ func newGlobalNotification(t *testing.T, description string) *notificationsv1.Gl
|
||||
SubKind: "test-subkind",
|
||||
Spec: ¬ificationsv1.NotificationSpec{},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Labels: map[string]string{"description": description},
|
||||
Labels: map[string]string{
|
||||
types.NotificationTitleLabel: title,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -38,11 +38,10 @@ func TestMarshalNotificationRoundTrip(t *testing.T) {
|
||||
Kind: types.KindNotification,
|
||||
Version: types.V1,
|
||||
SubKind: "test-subkind",
|
||||
Spec: ¬ificationsv1.NotificationSpec{
|
||||
Id: "test-notification-1",
|
||||
},
|
||||
Spec: ¬ificationsv1.NotificationSpec{},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Labels: map[string]string{"description": "description-1"},
|
||||
Name: "test-notification-1",
|
||||
Labels: map[string]string{types.NotificationTitleLabel: "title-1"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -67,11 +66,10 @@ func TestMarshalGlobalNotificationRoundTrip(t *testing.T) {
|
||||
},
|
||||
Notification: ¬ificationsv1.Notification{
|
||||
SubKind: "test-subkind",
|
||||
Spec: ¬ificationsv1.NotificationSpec{
|
||||
Id: "test-notification-id",
|
||||
},
|
||||
Spec: ¬ificationsv1.NotificationSpec{},
|
||||
Metadata: &headerv1.Metadata{
|
||||
Labels: map[string]string{"description": "description-1"},
|
||||
Name: "test-notification-id",
|
||||
Labels: map[string]string{types.NotificationTitleLabel: "title-1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user