From 31420a73df13f9c19339a1d89304b7d71b6224a2 Mon Sep 17 00:00:00 2001 From: harishkswami <168426010+harishkswami@users.noreply.github.com> Date: Wed, 28 Aug 2024 04:38:19 +0530 Subject: [PATCH] feat(notifier): lark support for guardian (#163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * lark support * fix: fix typo in mapstructure annotation * fix: map old notifier config to new multiple notifiers format * fix: fix slack client initialization with multiple workspaces * fix: fix lint errors * add json config for notifiers * add json config for notifiers * add json config for notifiers * add json config for notifiers * add json config for notifiers * add json config for notifiers * fix review commits * fix review commits * feat(email): handling email case sensitive (#165) * feat(email): handling email case sensitive * chore: handling list page * chore: handle case on filter --------- Co-authored-by: Muhammad Idil Haq Amir * feat(gate): introduce gate provider (#164) * feat(gate): introduce gate provider * fix: send gate api key in query param * fix: handle non-success http codes * feat(notifier): expose variables in notifier criteria (#166) * feat: add support for google_oauth2 token. Related to issue: https://… (#169) * feat: add support for google_oauth2 token. Related to issue: https://github.com/goto/guardian/issues/168 * add test cases * add test cases * add error formatting changes --------- Co-authored-by: anjali.agarwal * feat: add support for expression in request body when fetching metada… (#170) * feat: add support for expression in request body when fetching metadata sources for appeal * add a condition in http mock call --------- Co-authored-by: anjali.agarwal * ci: lock golangci-lint to v1.59 (#172) * ci: lock golangci-lint to v1.59 * ci: fix golangci-lint warnings * chore: fetch creator details before fetching metadata sources as they… (#174) chore: fetch creator details before fetching metadata sources as they are helpful Co-authored-by: anjali.agarwal --------- Co-authored-by: Rahmat Hidayat Co-authored-by: Muhammad Idil Haq Amir Co-authored-by: Muhammad Idil Haq Amir Co-authored-by: anjali9791 Co-authored-by: anjali.agarwal --- cli/job.go | 25 +- internal/server/config.go | 14 +- internal/server/config.yaml | 22 +- internal/server/server.go | 27 ++- plugins/notifiers/client.go | 110 ++++++++- plugins/notifiers/client_test.go | 101 ++++++++ plugins/notifiers/lark/client.go | 219 ++++++++++++++++++ .../lark/templates/AccessRevoked.json | 7 + .../lark/templates/AppealApproved.json | 7 + .../lark/templates/AppealRejected.json | 7 + .../lark/templates/ApproverNotification.json | 7 + .../lark/templates/ExpirationReminder.json | 7 + .../lark/templates/GrantOwnerChanged.json | 7 + .../notifiers/lark/templates/NewComment.json | 7 + .../templates/OnBehalfAppealApproved.json | 7 + .../templates/PendingApprovalsReminder.json | 7 + .../notifiers/lark/templates/UnusedGrant.json | 7 + 17 files changed, 570 insertions(+), 18 deletions(-) create mode 100644 plugins/notifiers/lark/client.go create mode 100644 plugins/notifiers/lark/templates/AccessRevoked.json create mode 100644 plugins/notifiers/lark/templates/AppealApproved.json create mode 100644 plugins/notifiers/lark/templates/AppealRejected.json create mode 100644 plugins/notifiers/lark/templates/ApproverNotification.json create mode 100644 plugins/notifiers/lark/templates/ExpirationReminder.json create mode 100644 plugins/notifiers/lark/templates/GrantOwnerChanged.json create mode 100644 plugins/notifiers/lark/templates/NewComment.json create mode 100644 plugins/notifiers/lark/templates/OnBehalfAppealApproved.json create mode 100644 plugins/notifiers/lark/templates/PendingApprovalsReminder.json create mode 100644 plugins/notifiers/lark/templates/UnusedGrant.json diff --git a/cli/job.go b/cli/job.go index ae5ff72e7..6b9bb3515 100644 --- a/cli/job.go +++ b/cli/job.go @@ -2,9 +2,11 @@ package cli import ( "context" + "encoding/json" "fmt" "github.com/goto/guardian/pkg/log" + "github.com/mitchellh/mapstructure" "github.com/MakeNowJust/heredoc" "github.com/go-playground/validator/v10" @@ -69,7 +71,28 @@ func runJobCmd() *cobra.Command { logger := log.NewCtxLogger(config.LogLevel, []string{config.AuditLogTraceIDHeaderKey}) crypto := crypto.NewAES(config.EncryptionSecretKeyKey) validator := validator.New() - notifier, err := notifiers.NewClient(&config.Notifier, logger) + var notifierMap map[string]interface{} + errr := json.Unmarshal([]byte(config.Notifiers), ¬ifierMap) + if errr != nil { + return fmt.Errorf("failed to parse notifier config: %w", errr) + } + var notifierConfigMap map[string]notifiers.Config + err = mapstructure.Decode(notifierMap, ¬ifierConfigMap) + if err != nil { + return fmt.Errorf("failed to parse notifier config: %w", err) + } + notifierConfig := []notifiers.Config{} + if config.Notifiers != "" { + for _, val := range notifierConfigMap { + notifierConfig = append(notifierConfig, val) + } + } else { + // map old to the new format + oldConfig := config.Notifier + oldConfig.Criteria = "true" + notifierConfig = append(notifierConfig, oldConfig) + } + notifier, err := notifiers.NewMultiClient(¬ifierConfig, logger) if err != nil { return err } diff --git a/internal/server/config.go b/internal/server/config.go index 662851e9e..c8dcbafac 100644 --- a/internal/server/config.go +++ b/internal/server/config.go @@ -32,12 +32,14 @@ type Jobs struct { } type Config struct { - Port int `mapstructure:"port" default:"8080"` - GRPC GRPCConfig `mapstructure:"grpc"` - EncryptionSecretKeyKey string `mapstructure:"encryption_secret_key"` - Notifier notifiers.Config `mapstructure:"notifier"` - LogLevel string `mapstructure:"log_level" default:"info"` - DB store.Config `mapstructure:"db"` + Port int `mapstructure:"port" default:"8080"` + GRPC GRPCConfig `mapstructure:"grpc"` + EncryptionSecretKeyKey string `mapstructure:"encryption_secret_key"` + Notifiers string `mapstructure:"notifiers"` + // Deprecated: use Notifiers instead + Notifier notifiers.Config `mapstructure:"notifier"` + LogLevel string `mapstructure:"log_level" default:"info"` + DB store.Config `mapstructure:"db"` // Deprecated: use Auth.Default.HeaderKey instead note on the AuthenticatedUserHeaderKey AuthenticatedUserHeaderKey string `mapstructure:"authenticated_user_header_key"` AuditLogTraceIDHeaderKey string `mapstructure:"audit_log_trace_id_header_key" default:"X-Trace-Id"` diff --git a/internal/server/config.yaml b/internal/server/config.yaml index 134bf86c8..f729d353c 100644 --- a/internal/server/config.yaml +++ b/internal/server/config.yaml @@ -27,13 +27,21 @@ DB: NAME: PORT: 5432 SSLMODE: disable -NOTIFIER: - PROVIDER: slack - ACCESS_TOKEN: - WORKSPACES: - - WORKSPACE: goto - ACCESS_TOKEN: - CRITERIA: "email contains '@goto'" +NOTIFIERS: | + { + "my_lark": { + "provider": "lark", + "client_id": "", + "client_secret": "", + "messages": {} + }, + "my_slack": { + "provider": "slack", + "access_token": "", + "messages": {} + } + } + JOBS: REVOKE_GRANTS_BY_USER_CRITERIA: CONFIG: diff --git a/internal/server/server.go b/internal/server/server.go index eac4ecd48..0f8db794c 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -10,6 +10,8 @@ import ( "github.com/goto/guardian/domain" + "encoding/json" + "github.com/go-playground/validator/v10" handlerv1beta1 "github.com/goto/guardian/api/handler/v1beta1" guardianv1beta1 "github.com/goto/guardian/api/proto/gotocompany/guardian/v1beta1" @@ -25,6 +27,7 @@ import ( grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus" grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/mitchellh/mapstructure" "github.com/sirupsen/logrus" "github.com/uptrace/opentelemetry-go-extra/otelgorm" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" @@ -49,7 +52,29 @@ func RunServer(config *Config) error { logger := log.NewCtxLogger(config.LogLevel, []string{domain.TraceIDKey}) crypto := crypto.NewAES(config.EncryptionSecretKeyKey) validator := validator.New() - notifier, err := notifiers.NewClient(&config.Notifier, logger) + + var notifierMap map[string]interface{} + err := json.Unmarshal([]byte(config.Notifiers), ¬ifierMap) + if err != nil { + return fmt.Errorf("failed to parse notifier config: %w", err) + } + var notifierConfigMap map[string]notifiers.Config + err = mapstructure.Decode(notifierMap, ¬ifierConfigMap) + if err != nil { + return fmt.Errorf("failed to parse notifier config: %w", err) + } + notifierConfig := []notifiers.Config{} + if config.Notifiers != "" { + for _, val := range notifierConfigMap { + notifierConfig = append(notifierConfig, val) + } + } else { + // map old to the new format + oldConfig := config.Notifier + oldConfig.Criteria = "true" + notifierConfig = append(notifierConfig, oldConfig) + } + notifier, err := notifiers.NewMultiClient(¬ifierConfig, logger) if err != nil { return err } diff --git a/plugins/notifiers/client.go b/plugins/notifiers/client.go index deb4b27ab..37d5f34f5 100644 --- a/plugins/notifiers/client.go +++ b/plugins/notifiers/client.go @@ -7,19 +7,53 @@ import ( "net/http" "time" + "github.com/goto/guardian/pkg/evaluator" "github.com/goto/guardian/pkg/log" + "github.com/goto/guardian/plugins/notifiers/lark" + "github.com/goto/guardian/plugins/notifiers/slack" "github.com/mitchellh/mapstructure" "github.com/goto/guardian/domain" - "github.com/goto/guardian/plugins/notifiers/slack" ) type Client interface { Notify(context.Context, []domain.Notification) []error } +type NotifyManager struct { + clients []Client + configs []Config +} + +func (m *NotifyManager) Notify(ctx context.Context, notification []domain.Notification) []error { + var errs []error + for i, client := range m.clients { + // evaluate criteria + config := m.configs[i] + v, err := evaluator.Expression(config.Criteria).EvaluateWithVars(map[string]interface{}{ + "email": notification[0].User, + }) + if err != nil { + errs = append(errs, err) + continue + } + + // if the expression evaluates to true, notify the client + if match, ok := v.(bool); !ok { + errs = append(errs, fmt.Errorf("notifier expression did not evaluate to a boolean: %s", config.Criteria)) + } else if match { + if notifyErrs := client.Notify(ctx, notification); notifyErrs != nil { + errs = append(errs, notifyErrs...) + } + } + + } + return errs +} + const ( ProviderTypeSlack = "slack" + ProviderTypeLark = "lark" ) // SlackConfig is a map of workspace name to config @@ -30,16 +64,47 @@ func (c SlackConfig) Decode(v interface{}) error { } type Config struct { - Provider string `mapstructure:"provider" validate:"omitempty,oneof=slack"` + Provider string `mapstructure:"provider" validate:"omitempty,oneof=slack lark"` + Name string `mapstructure:"name"` + ClientID string `mapstructure:"client_id,omitempty"` + ClientSecret string `mapstructure:"client_secret,omitempty"` + Criteria string `mapstructure:"criteria"` // slack AccessToken string `mapstructure:"access_token" validate:"required_without=SlackConfig"` SlackConfig SlackConfig `mapstructure:"slack_config" validate:"required_without=AccessToken,dive"` - // custom messages Messages domain.NotificationMessages } +func NewMultiClient(notifiers *[]Config, logger log.Logger) (*NotifyManager, error) { + notifyManager := &NotifyManager{} + for _, notifier := range *notifiers { + if notifier.Provider == ProviderTypeSlack { + slackConfig, err := NewSlackConfig(¬ifier) + if err != nil { + return nil, err + } + httpClient := &http.Client{Timeout: 10 * time.Second} + slackClient := slack.NewNotifier(slackConfig, httpClient, logger) + notifyManager.addClient(slackClient) + notifyManager.addNotifier(notifier) + } + if notifier.Provider == ProviderTypeLark { + larkConfig, err := getLarkConfig(¬ifier, notifier.Messages) + if err != nil { + return nil, err + } + httpClient := &http.Client{Timeout: 10 * time.Second} + larkClient := lark.NewNotifier(larkConfig, httpClient, logger) + notifyManager.addClient(larkClient) + notifyManager.addNotifier(notifier) + } + } + + return notifyManager, nil +} + func NewClient(config *Config, logger log.Logger) (Client, error) { if config.Provider == ProviderTypeSlack { slackConfig, err := NewSlackConfig(config) @@ -92,3 +157,42 @@ func NewSlackConfig(config *Config) (*slack.Config, error) { return slackConfig, nil } + +func getLarkConfig(config *Config, messages domain.NotificationMessages) (*lark.Config, error) { + // validation + if config.ClientID == "" && config.ClientSecret == "" { + return nil, errors.New("lark clientid & clientSecret must be provided") + } + if config.ClientID == "" && config.ClientSecret != "" { + return nil, errors.New("lark clientid & clientSecret must be provided") + } + if config.ClientID != "" && config.ClientSecret == "" { + return nil, errors.New("lark clientid & clientSecret must be provided") + } + + var larkConfig *lark.Config + if config.ClientID != "" { + workspace := lark.LarkWorkspace{ + WorkspaceName: config.Provider, + ClientID: config.ClientID, + ClientSecret: config.ClientSecret, + Criteria: config.Criteria, + } + larkConfig = &lark.Config{ + Workspace: workspace, + Messages: messages, + } + return larkConfig, nil + + } + + return larkConfig, nil +} + +func (nm *NotifyManager) addClient(client Client) { + nm.clients = append(nm.clients, client) +} + +func (nm *NotifyManager) addNotifier(notifier Config) { + nm.configs = append(nm.configs, notifier) +} diff --git a/plugins/notifiers/client_test.go b/plugins/notifiers/client_test.go index e07b54d55..1487c295e 100644 --- a/plugins/notifiers/client_test.go +++ b/plugins/notifiers/client_test.go @@ -4,6 +4,8 @@ import ( "reflect" "testing" + "github.com/goto/guardian/domain" + "github.com/goto/guardian/plugins/notifiers/lark" "github.com/goto/guardian/plugins/notifiers/slack" ) @@ -114,3 +116,102 @@ func TestNewSlackConfig(t *testing.T) { }) } } + +func TestNewSlackLarkConfig(t *testing.T) { + type args struct { + config Config + } + tests := []struct { + name string + args args + want *lark.Config + wantErr bool + }{ + { + name: "should return lark config when clientid is provided", + args: args{ + config: Config{ + Provider: "lark", + AccessToken: "", + ClientID: "foo", + ClientSecret: "foo", + Criteria: "$email contains '@gojek'", + }, + }, + want: &lark.Config{ + Workspace: lark.LarkWorkspace{ + WorkspaceName: "lark", + ClientID: "foo", + ClientSecret: "foo", + Criteria: "$email contains '@gojek'", + }, + Messages: domain.NotificationMessages{}, + }, + wantErr: false, + }, + { + name: "should return error when no Client id or workspaces are provided", + args: args{ + config: Config{ + Provider: "provider", + AccessToken: "config.Notifier.AccessToken", + ClientID: "", + ClientSecret: "", + Criteria: ".send_to_slack == true", + }, + }, + want: nil, + wantErr: true, + }, { + name: "should return error when both Client id and workspaces are provided", + args: args{ + config: Config{ + Provider: "provider", + AccessToken: "config.Notifier.AccessToken", + ClientID: "", + ClientSecret: "", + Criteria: ".send_to_slack == true", + }, + }, + want: nil, + wantErr: true, + }, { + name: "should return lark config when workspaces are provided", + args: args{ + config: Config{ + Provider: "provider", + AccessToken: "config.Notifier.AccessToken", + ClientID: "foo", + ClientSecret: "foo", + Criteria: ".send_to_slack == true", + }, + }, + want: &lark.Config{ + Workspace: lark.LarkWorkspace{ + WorkspaceName: "provider", + ClientID: "foo", + ClientSecret: "foo", + Criteria: ".send_to_slack == true", + }, + Messages: domain.NotificationMessages{}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + + t.Run(tt.name, func(t *testing.T) { + + got, err := getLarkConfig(&tt.args.config, domain.NotificationMessages{}) + + if (err != nil) != tt.wantErr { + t.Errorf("NewLarkConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewLarkConfig() got = %v, want %v", got, tt.want) + } + + }) + } +} diff --git a/plugins/notifiers/lark/client.go b/plugins/notifiers/lark/client.go new file mode 100644 index 000000000..d31b14216 --- /dev/null +++ b/plugins/notifiers/lark/client.go @@ -0,0 +1,219 @@ +package lark + +import ( + "bytes" + "context" + "embed" + "encoding/json" + "errors" + "fmt" + "html/template" + "net/http" + + "github.com/goto/guardian/pkg/log" + + "github.com/goto/guardian/utils" + + "github.com/goto/guardian/domain" +) + +const ( + larkHost = "https://open.larksuite.com" +) + +type tokenResponse struct { + OK string `json:"msg"` + Token string `json:"tenant_access_token"` + Code int `json:"code"` + Expire int `json:"expire"` +} + +type Payload struct { + AppID string `json:"app_id"` + AppSecret string `json:"app_secret"` +} + +type WorkSpaceConfig struct { + Workspaces []LarkWorkspace `mapstructure:"workspaces"` +} + +type LarkWorkspace struct { + WorkspaceName string `mapstructure:"workspace" validate:"required"` + ClientID string `mapstructure:"client_id" validate:"required"` + ClientSecret string `mapstructure:"client_secret" validate:"required"` + Criteria string `mapstructure:"criteria" validate:"required"` +} + +type Notifier struct { + workspace LarkWorkspace + Messages domain.NotificationMessages + httpClient utils.HTTPClient + defaultMessageFiles embed.FS + logger log.Logger +} + +type Config struct { + Workspace LarkWorkspace + Messages domain.NotificationMessages +} + +//go:embed templates/* +var defaultTemplates embed.FS + +func NewNotifier(config *Config, httpClient utils.HTTPClient, logger log.Logger) *Notifier { + return &Notifier{ + workspace: config.Workspace, + Messages: config.Messages, + httpClient: httpClient, + defaultMessageFiles: defaultTemplates, + logger: logger, + } +} + +func (n *Notifier) Notify(ctx context.Context, items []domain.Notification) []error { + errs := make([]error, 0) + for _, item := range items { + var larkWorkspace *LarkWorkspace + email := item.User + labelSlice := utils.MapToSlice(item.Labels) + larkWorkspace = &n.workspace + + n.logger.Debug(ctx, fmt.Sprintf("%v | sending lark notification to user:%s ", labelSlice, item.User)) + msg, err := ParseMessage(item.Message, n.Messages, n.defaultMessageFiles) + if err != nil { + errs = append(errs, fmt.Errorf("%v | error parsing message : %w", labelSlice, err)) + continue + } + + if err := n.sendMessage(*larkWorkspace, email, msg); err != nil { + errs = append(errs, fmt.Errorf("%v | error sending message to user:%s in workspace:%s | %w", labelSlice, item.User, larkWorkspace.WorkspaceName, err)) + continue + } + } + + return errs +} + +func (n *Notifier) sendMessage(workspace LarkWorkspace, email string, messageBlock string) error { + url := larkHost + "/open-apis/im/v1/messages?receive_id_type=email" + var messageblockList []interface{} + if err := json.Unmarshal([]byte(messageBlock), &messageblockList); err != nil { + return fmt.Errorf("error in parsing message block %s", err) + } + var payload map[string]interface{} + var messages []map[string]interface{} + err := json.Unmarshal([]byte(messageBlock), &messages) + for _, message := range messages { + payload = message + } + if err != nil { + return err + } + payload["receive_id"] = email + data, err := json.Marshal(payload) + if err != nil { + return err + } + + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data)) + if err != nil { + return err + } + // create tanent_access_token + token, err := n.findTenantAccessToken(workspace.ClientID, workspace.ClientSecret, workspace) + if err != nil { + return err + } + req.Header.Add("Authorization", "Bearer "+token) + req.Header.Add("Content-Type", "application/json") + _, err = n.sendRequest(req) + return err +} + +func (n *Notifier) findTenantAccessToken(clientId string, clientSecret string, ws LarkWorkspace) (string, error) { + larkURL := larkHost + "/open-apis/auth/v3/tenant_access_token/internal/" + payload := Payload{ + AppID: clientId, + AppSecret: clientSecret, + } + data, err := json.Marshal(payload) + if err != nil { + return "", err + } + req, err := http.NewRequest(http.MethodPost, larkURL, bytes.NewBuffer(data)) + if err != nil { + return "", err + } + req.Header.Add("Content-Type", "application/json") + result, err := n.sendRequest(req) + if err != nil { + return "", fmt.Errorf("error get tenant access token for workspace: %s - %s", ws.WorkspaceName, err) + } + if result.OK != "ok" { + return "", fmt.Errorf("could not get token for workspace: %s - %s", ws.WorkspaceName, result.OK) + } + return result.Token, nil +} + +func (n *Notifier) sendRequest(req *http.Request) (*tokenResponse, error) { + + resp, err := n.httpClient.Do(req) + if err != nil { + return nil, err + } + var result tokenResponse + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + return nil, err + } + if result.OK != "ok" { + return &result, errors.New(result.OK) + } + return &result, nil +} + +func getDefaultTemplate(messageType string, defaultTemplateFiles embed.FS) (string, error) { + content, err := defaultTemplateFiles.ReadFile(fmt.Sprintf("templates/%s.json", messageType)) + if err != nil { + return "", fmt.Errorf("error finding default template for message type %s - %s", messageType, err) + } + return string(content), nil +} + +func ParseMessage(message domain.NotificationMessage, templates domain.NotificationMessages, defaultTemplateFiles embed.FS) (string, error) { + messageTypeTemplateMap := map[string]string{ + domain.NotificationTypeAccessRevoked: templates.AccessRevoked, + domain.NotificationTypeAppealApproved: templates.AppealApproved, + domain.NotificationTypeAppealRejected: templates.AppealRejected, + domain.NotificationTypeApproverNotification: templates.ApproverNotification, + domain.NotificationTypeExpirationReminder: templates.ExpirationReminder, + domain.NotificationTypeOnBehalfAppealApproved: templates.OthersAppealApproved, + domain.NotificationTypeGrantOwnerChanged: templates.GrantOwnerChanged, + domain.NotificationTypeNewComment: templates.NewComment, + domain.NotificationTypePendingApprovalsReminder: templates.PendingApprovalsReminder, + } + + messageBlock, ok := messageTypeTemplateMap[message.Type] + if !ok { + return "", fmt.Errorf("template not found for message type %s", message.Type) + } + + if messageBlock == "" { + defaultMsgBlock, err := getDefaultTemplate(message.Type, defaultTemplateFiles) + if err != nil { + return "", err + } + messageBlock = defaultMsgBlock + } + t, err := template.New("notification_messages").Parse(messageBlock) + if err != nil { + return "", err + } + + var buff bytes.Buffer + if err := t.Execute(&buff, message.Variables); err != nil { + return "", err + } + + return buff.String(), nil +} diff --git a/plugins/notifiers/lark/templates/AccessRevoked.json b/plugins/notifiers/lark/templates/AccessRevoked.json new file mode 100644 index 000000000..d01f92406 --- /dev/null +++ b/plugins/notifiers/lark/templates/AccessRevoked.json @@ -0,0 +1,7 @@ +[ + { + "receive_id":"{{.requestor}}", + "msg_type":"interactive", + "content":"{\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"elements\": [\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"Your access to **{{.resource_name}}}** with role **{{.role}}** has been revoked\"\n }\n }\n ]\n }\n \n ]\n}" + } +] \ No newline at end of file diff --git a/plugins/notifiers/lark/templates/AppealApproved.json b/plugins/notifiers/lark/templates/AppealApproved.json new file mode 100644 index 000000000..86be3d8b4 --- /dev/null +++ b/plugins/notifiers/lark/templates/AppealApproved.json @@ -0,0 +1,7 @@ +[ + { + "receive_id":"{{.requestor}}", + "msg_type":"interactive", + "content":"{\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"elements\": [\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"Your appeal to **{{.resource_name}}** with role **{{.role}}** has been approved\"\n }\n }\n ]\n }\n \n ]\n}" + } +] \ No newline at end of file diff --git a/plugins/notifiers/lark/templates/AppealRejected.json b/plugins/notifiers/lark/templates/AppealRejected.json new file mode 100644 index 000000000..cdbb0b12d --- /dev/null +++ b/plugins/notifiers/lark/templates/AppealRejected.json @@ -0,0 +1,7 @@ +[ + { + "receive_id":"{{.requestor}}}", + "msg_type":"interactive", + "content":"{\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"elements\": [\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"Your appeal to **{{.resource_name}}** with role **{{.role}}** has been rejected\"\n }\n }\n ]\n }\n \n ]\n}" + } +] \ No newline at end of file diff --git a/plugins/notifiers/lark/templates/ApproverNotification.json b/plugins/notifiers/lark/templates/ApproverNotification.json new file mode 100644 index 000000000..475897393 --- /dev/null +++ b/plugins/notifiers/lark/templates/ApproverNotification.json @@ -0,0 +1,7 @@ +[ + { + "receive_id":"{{.requestor}}", + "content": "{\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"header\": {\n \"title\": {\n \"tag\": \"plain_text\",\n \"content\": \"You have an appeal created by *{{.requestor}}* requesting access to **{{.resource_name}}** with role **{{.role}}**. Appeal ID: **{{.appeal_id}}**\"\n }\n },\n \"elements\": [\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**Provider:**\\n{{.provider_type}}\"\n }\n },\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**Resource Type:**\\n{{.resource_type}}\"\n }\n },\n{\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**Resource:**\\n{{.resource_name}}\"\n }\n },\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**Account Id:**\\n{{.account_id}}\"\n }\n },\n{\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**Role:**\\n{{.role}}\"\n }\n },\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**Duration:**\\n{{.created_at}}\"\n }\n },\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**Console link:**\\nhttps://console.integration.golabs.io/requests/{{.appeal_id}}\"\n }\n }\n ]\n },\n {\n \"tag\": \"hr\"\n },\n {\n \"tag\": \"action\",\n \"layout\": \"bisected\",\n \"actions\": [\n {\n \"tag\": \"button\",\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"Approve\"\n },\n \"url\":\"https://console.integration.golabs.io/dataaccess/appeal_action?action=approve&appeal_id={{.appeal_id}}&approval_step={{.approval_step}}&actor={{.actor}}\",\n \"type\":\"primary\"\n },\n {\n \"tag\": \"button\",\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"Reject\"\n },\n \"url\":\"https://console.integration.golabs.io/dataaccess/appeal_action?action=reject&appeal_id={{.appeal_id}}&approval_step={{.approval_step}}&actor={{.actor}}\",\n \"type\":\"danger\"\n }\n ]\n }\n ]\n}\n", + "msg_type": "interactive" + } +] \ No newline at end of file diff --git a/plugins/notifiers/lark/templates/ExpirationReminder.json b/plugins/notifiers/lark/templates/ExpirationReminder.json new file mode 100644 index 000000000..573621c03 --- /dev/null +++ b/plugins/notifiers/lark/templates/ExpirationReminder.json @@ -0,0 +1,7 @@ +[ + { + "receive_id":"{{.requestor}}", + "msg_type":"interactive", + "content":"{\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"elements\": [\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"Your access *{{.account_id}}* to *{{.resource_name}}* with role **{{.role}}** will expire at **{{.expiration_date}}**. Extend the access if it's still needed\"\n }\n }\n ]\n }\n \n ]\n}" + } +] \ No newline at end of file diff --git a/plugins/notifiers/lark/templates/GrantOwnerChanged.json b/plugins/notifiers/lark/templates/GrantOwnerChanged.json new file mode 100644 index 000000000..d7bc16129 --- /dev/null +++ b/plugins/notifiers/lark/templates/GrantOwnerChanged.json @@ -0,0 +1,7 @@ +[ + { + "receive_id":"{{.requestor}}", + "msg_type":"interactive", + "content":"{\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"elements\": [\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"Owner of grant **{{.grant_id}}** has been changed from **{{.previous_owner}}** to **{{.new_owner}}**\"\n }\n }\n ]\n }\n \n ]\n}" + } +] \ No newline at end of file diff --git a/plugins/notifiers/lark/templates/NewComment.json b/plugins/notifiers/lark/templates/NewComment.json new file mode 100644 index 000000000..110e2b070 --- /dev/null +++ b/plugins/notifiers/lark/templates/NewComment.json @@ -0,0 +1,7 @@ +[ + { + "receive_id":"{{.requestor}}", + "msg_type":"interactive", + "content":"{\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"elements\": [\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**{{.comment_created_by}}** commented on an appeal requested by **{{.appeal_created_by}}**\"\n }\n }\n ]\n },\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**Requested resource**:\"\n }\n }\n ]\n },\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"{{.resource_name}}\"\n }\n }\n ]\n },\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**Comment**:\"\n }\n }\n ]\n },\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"{{.body}}\"\n }\n }\n ]\n },\n {\n \"tag\": \"hr\"\n },\n {\n \"tag\": \"action\",\n \"layout\": \"bisected\",\n \"actions\": [\n {\n \"tag\": \"button\",\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"View it on Console\"\n },\n \"url\": \"https://console.integration.golabs.io/access/requests/{{.appeal_id}}#{{.comment_id}}\",\n \"type\": \"primary\"\n }\n ]\n }\n ]\n}" + } +] \ No newline at end of file diff --git a/plugins/notifiers/lark/templates/OnBehalfAppealApproved.json b/plugins/notifiers/lark/templates/OnBehalfAppealApproved.json new file mode 100644 index 000000000..87d5971e9 --- /dev/null +++ b/plugins/notifiers/lark/templates/OnBehalfAppealApproved.json @@ -0,0 +1,7 @@ +[ + { + "receive_id":"{{.requestor}}", + "msg_type":"interactive", + "content":"{\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"elements\": [\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"Your appeal to **{{.resource_name}}** with role **{{.role}}** created by **{{.requestor}}** has been approved\"\n }\n }\n ]\n }\n \n ]\n}" + } +] \ No newline at end of file diff --git a/plugins/notifiers/lark/templates/PendingApprovalsReminder.json b/plugins/notifiers/lark/templates/PendingApprovalsReminder.json new file mode 100644 index 000000000..740d07b73 --- /dev/null +++ b/plugins/notifiers/lark/templates/PendingApprovalsReminder.json @@ -0,0 +1,7 @@ +[ + { + "receive_id":"{{.requestor}}", + "msg_type":"interactive", + "content":"{\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"elements\": [\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"Hi **{{.approver}}**, you have {{.pending_approvals_count}} pending requests waiting for your approval on Console Access.\"\n }\n }\n ]\n },\n{\n \"tag\": \"hr\"\n },\n {\n \"tag\": \"action\",\n \"layout\": \"bisected\",\n \"actions\": [\n \n {\n \"tag\": \"button\",\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"View it on console\"\n },\n \"url\":\"https://console.integration.golabs.io/access/manage-requests\",\n \"type\":\"primary\",\n \"value\": {\n \"chosen\": \"manage-requests-page\"\n }\n }\n ]\n }\n \n ]\n}" + } +] \ No newline at end of file diff --git a/plugins/notifiers/lark/templates/UnusedGrant.json b/plugins/notifiers/lark/templates/UnusedGrant.json new file mode 100644 index 000000000..c43edcabc --- /dev/null +++ b/plugins/notifiers/lark/templates/UnusedGrant.json @@ -0,0 +1,7 @@ +[ + { + "receive_id":"{{.requestor}}", + "msg_type":"interactive", + "content":"{\n \"config\": {\n \"wide_screen_mode\": true\n },\n \"elements\": [\n {\n \"tag\": \"div\",\n \"fields\": [\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"We have advanced the expiration date for the following grants due to inactivity since {{.start_date}}:{{ range .dormant_grants }}\"\n }\n },\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \"**ID**: {{.id}}\"\n }\n },\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \">**Account ID**: {{.account_id}}\"\n }\n },\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \">**Resource**: {{.resource.urn}} ({{.resource.provider_type}} {{.resource.type}})\"\n }\n },\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \">**Role**: {{.role}}\"\n }\n },\n {\n \"is_short\": true,\n \"text\": {\n \"tag\": \"lark_md\",\n \"content\": \">**Expiration Date** (new): {{.expiration_date}}\"\n }\n }\n ]\n }\n ]\n}" + } +] \ No newline at end of file