From b06c2292bd300fa87bb513d9d042b3eb94bffa8a Mon Sep 17 00:00:00 2001 From: Edward Dowling Date: Thu, 17 Apr 2025 16:10:38 +0100 Subject: [PATCH 1/7] Add max duration to msteams plugin cards --- integrations/access/msteams/app.go | 1 + integrations/access/msteams/card.go | 1 + integrations/lib/plugindata/access_request.go | 11 +++++++++++ 3 files changed, 13 insertions(+) diff --git a/integrations/access/msteams/app.go b/integrations/access/msteams/app.go index 4e9c577b34c95..c702c9432042d 100644 --- a/integrations/access/msteams/app.go +++ b/integrations/access/msteams/app.go @@ -302,6 +302,7 @@ func (a *App) onPendingRequest(ctx context.Context, req types.AccessRequest) err User: req.GetUser(), Roles: req.GetRoles(), RequestReason: req.GetRequestReason(), + MaxDuration: req.GetMaxDuration(), } log := a.log.With("request_id", id) diff --git a/integrations/access/msteams/card.go b/integrations/access/msteams/card.go index 93945d35cca29..dce9b8ebc0c96 100644 --- a/integrations/access/msteams/card.go +++ b/integrations/access/msteams/card.go @@ -52,6 +52,7 @@ func BuildCard(id string, webProxyURL *url.URL, clusterName string, data plugind {Title: "Cluster", Value: clusterName}, {Title: "User", Value: data.User}, {Title: "Role(s)", Value: strings.Join(data.Roles, ", ")}, + {Title: "Max Duration", Value: data.MaxDuration.String()}, } if data.RequestReason != "" { diff --git a/integrations/lib/plugindata/access_request.go b/integrations/lib/plugindata/access_request.go index fd70d44927144..c20d2accc1079 100644 --- a/integrations/lib/plugindata/access_request.go +++ b/integrations/lib/plugindata/access_request.go @@ -22,6 +22,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "github.com/gravitational/trace" ) @@ -49,6 +50,7 @@ type AccessRequestData struct { Resources []string SuggestedReviewers []string LoginsByRole map[string][]string + MaxDuration time.Time } // DecodeAccessRequestData deserializes a string map to PluginData struct. @@ -63,6 +65,14 @@ func DecodeAccessRequestData(dataMap map[string]string) (data AccessRequestData, } data.ResolutionTag = ResolutionTag(dataMap["resolution"]) data.ResolutionReason = dataMap["resolve_reason"] + if str := dataMap["max_duration"]; str != "" { + data.MaxDuration, err = time.Parse(time.RFC3339, str) + if err != nil { + err = trace.Wrap(err) + return + } + + } if str, ok := dataMap["resources"]; ok { err = json.Unmarshal([]byte(str), &data.Resources) @@ -131,6 +141,7 @@ func EncodeAccessRequestData(data AccessRequestData) (map[string]string, error) result["reviews_count"] = reviewsCountStr result["resolution"] = string(data.ResolutionTag) result["resolve_reason"] = data.ResolutionReason + result["max_duration"] = data.MaxDuration.String() if len(data.SystemAnnotations) != 0 { annotaions, err := json.Marshal(data.SystemAnnotations) From 745633ce6503354f9e6f0f4a9b290fa006715703 Mon Sep 17 00:00:00 2001 From: Edward Dowling Date: Wed, 23 Apr 2025 16:24:41 +0100 Subject: [PATCH 2/7] Add test coverage to max_duration addition to plugindata --- .../access/accessrequest/plugindata_test.go | 43 +++++++++++-------- integrations/lib/plugindata/access_request.go | 11 +++-- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/integrations/access/accessrequest/plugindata_test.go b/integrations/access/accessrequest/plugindata_test.go index 3234763dd541a..d0e9166ab737d 100644 --- a/integrations/access/accessrequest/plugindata_test.go +++ b/integrations/access/accessrequest/plugindata_test.go @@ -20,32 +20,39 @@ package accessrequest import ( "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/gravitational/teleport/integrations/lib/plugindata" ) -var samplePluginData = PluginData{ - AccessRequestData: plugindata.AccessRequestData{ - User: "user-foo", - Roles: []string{"role-foo", "role-bar"}, - Resources: []string{"cluster-a/node/foo", "cluster-a/node/bar"}, - RequestReason: "foo reason", - ReviewsCount: 3, - ResolutionTag: plugindata.ResolvedApproved, - ResolutionReason: "foo ok", - }, - SentMessages: SentMessages{ - {ChannelID: "CHANNEL1", MessageID: "0000001"}, - {ChannelID: "CHANNEL2", MessageID: "0000002"}, - }, +func getSamplePluginData(t *testing.T) PluginData { + maxDuration, err := time.Parse(time.RFC3339,"2006-01-02T15:04:05Z") + require.NoError(t, err) + return PluginData{ + AccessRequestData: plugindata.AccessRequestData{ + User: "user-foo", + Roles: []string{"role-foo", "role-bar"}, + Resources: []string{"cluster-a/node/foo", "cluster-a/node/bar"}, + RequestReason: "foo reason", + ReviewsCount: 3, + ResolutionTag: plugindata.ResolvedApproved, + ResolutionReason: "foo ok", + MaxDuration: &maxDuration, + }, + SentMessages: SentMessages{ + {ChannelID: "CHANNEL1", MessageID: "0000001"}, + {ChannelID: "CHANNEL2", MessageID: "0000002"}, + }, + } } func TestEncodePluginData(t *testing.T) { - dataMap, err := EncodePluginData(samplePluginData) + dataMap, err := EncodePluginData(getSamplePluginData(t)) assert.NoError(t, err) - assert.Len(t, dataMap, 8) + assert.Len(t, dataMap, 9) assert.Equal(t, "user-foo", dataMap["user"]) assert.Equal(t, "role-foo,role-bar", dataMap["roles"]) assert.Equal(t, `["cluster-a/node/foo","cluster-a/node/bar"]`, dataMap["resources"]) @@ -54,6 +61,7 @@ func TestEncodePluginData(t *testing.T) { assert.Equal(t, "APPROVED", dataMap["resolution"]) assert.Equal(t, "foo ok", dataMap["resolve_reason"]) assert.Equal(t, "CHANNEL1/0000001,CHANNEL2/0000002", dataMap["messages"]) + assert.Equal(t, "2006-01-02T15:04:05Z", dataMap["max_duration"]) } func TestDecodePluginData(t *testing.T) { @@ -66,9 +74,10 @@ func TestDecodePluginData(t *testing.T) { "resolution": "APPROVED", "resolve_reason": "foo ok", "messages": "CHANNEL1/0000001,CHANNEL2/0000002", + "max_duration": "2006-01-02T15:04:05Z", }) assert.NoError(t, err) - assert.Equal(t, samplePluginData, pluginData) + assert.Equal(t, getSamplePluginData(t), pluginData) } func TestEncodeEmptyPluginData(t *testing.T) { diff --git a/integrations/lib/plugindata/access_request.go b/integrations/lib/plugindata/access_request.go index c20d2accc1079..2217357d356f2 100644 --- a/integrations/lib/plugindata/access_request.go +++ b/integrations/lib/plugindata/access_request.go @@ -50,7 +50,7 @@ type AccessRequestData struct { Resources []string SuggestedReviewers []string LoginsByRole map[string][]string - MaxDuration time.Time + MaxDuration *time.Time } // DecodeAccessRequestData deserializes a string map to PluginData struct. @@ -66,12 +66,13 @@ func DecodeAccessRequestData(dataMap map[string]string) (data AccessRequestData, data.ResolutionTag = ResolutionTag(dataMap["resolution"]) data.ResolutionReason = dataMap["resolve_reason"] if str := dataMap["max_duration"]; str != "" { - data.MaxDuration, err = time.Parse(time.RFC3339, str) + var maxDuration time.Time + maxDuration, err = time.Parse(time.RFC3339, str) if err != nil { err = trace.Wrap(err) return } - + data.MaxDuration = &maxDuration } if str, ok := dataMap["resources"]; ok { @@ -141,7 +142,9 @@ func EncodeAccessRequestData(data AccessRequestData) (map[string]string, error) result["reviews_count"] = reviewsCountStr result["resolution"] = string(data.ResolutionTag) result["resolve_reason"] = data.ResolutionReason - result["max_duration"] = data.MaxDuration.String() + if data.MaxDuration != nil { + result["max_duration"] = data.MaxDuration.Format(time.RFC3339) + } if len(data.SystemAnnotations) != 0 { annotaions, err := json.Marshal(data.SystemAnnotations) From e5bb3e294939d05ccb05d53eb831b19f7c6ef338 Mon Sep 17 00:00:00 2001 From: Edward Dowling Date: Fri, 25 Apr 2025 15:30:44 +0100 Subject: [PATCH 3/7] Fix incorrect usage of maxDuration in msteams plugin --- integrations/access/msteams/app.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integrations/access/msteams/app.go b/integrations/access/msteams/app.go index c702c9432042d..2499f5c1e07e8 100644 --- a/integrations/access/msteams/app.go +++ b/integrations/access/msteams/app.go @@ -298,11 +298,12 @@ func (a *App) onWatcherEvent(ctx context.Context, event types.Event) error { // onPendingRequest is called when there's a new request or a review func (a *App) onPendingRequest(ctx context.Context, req types.AccessRequest) error { id := req.GetName() + maxDuration := req.GetMaxDuration() data := pd.AccessRequestData{ User: req.GetUser(), Roles: req.GetRoles(), RequestReason: req.GetRequestReason(), - MaxDuration: req.GetMaxDuration(), + MaxDuration: &maxDuration, } log := a.log.With("request_id", id) From 9792c539edb8199a776f04e70437a6b134db21f7 Mon Sep 17 00:00:00 2001 From: Edward Dowling Date: Mon, 28 Apr 2025 11:05:16 +0100 Subject: [PATCH 4/7] Update integrations/lib/plugindata/access_request.go Co-authored-by: Tiago Silva --- integrations/lib/plugindata/access_request.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/integrations/lib/plugindata/access_request.go b/integrations/lib/plugindata/access_request.go index 2217357d356f2..02d6e020becd5 100644 --- a/integrations/lib/plugindata/access_request.go +++ b/integrations/lib/plugindata/access_request.go @@ -69,8 +69,7 @@ func DecodeAccessRequestData(dataMap map[string]string) (data AccessRequestData, var maxDuration time.Time maxDuration, err = time.Parse(time.RFC3339, str) if err != nil { - err = trace.Wrap(err) - return + return trace.Wrap(err) } data.MaxDuration = &maxDuration } From 98d0ccd760d1a50536110bd4c646c1f3d7f6c681 Mon Sep 17 00:00:00 2001 From: Edward Dowling Date: Mon, 28 Apr 2025 11:09:51 +0100 Subject: [PATCH 5/7] Fix error return from access request plugin data --- integrations/lib/plugindata/access_request.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integrations/lib/plugindata/access_request.go b/integrations/lib/plugindata/access_request.go index 02d6e020becd5..2217357d356f2 100644 --- a/integrations/lib/plugindata/access_request.go +++ b/integrations/lib/plugindata/access_request.go @@ -69,7 +69,8 @@ func DecodeAccessRequestData(dataMap map[string]string) (data AccessRequestData, var maxDuration time.Time maxDuration, err = time.Parse(time.RFC3339, str) if err != nil { - return trace.Wrap(err) + err = trace.Wrap(err) + return } data.MaxDuration = &maxDuration } From 478ec886adb8843e754fd5d207e7e37ecbb4288b Mon Sep 17 00:00:00 2001 From: Edward Dowling Date: Mon, 28 Apr 2025 11:41:27 +0100 Subject: [PATCH 6/7] Fix formating in plugindata tests --- integrations/access/accessrequest/plugindata_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/access/accessrequest/plugindata_test.go b/integrations/access/accessrequest/plugindata_test.go index d0e9166ab737d..68ed073d90e9d 100644 --- a/integrations/access/accessrequest/plugindata_test.go +++ b/integrations/access/accessrequest/plugindata_test.go @@ -29,7 +29,7 @@ import ( ) func getSamplePluginData(t *testing.T) PluginData { - maxDuration, err := time.Parse(time.RFC3339,"2006-01-02T15:04:05Z") + maxDuration, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z") require.NoError(t, err) return PluginData{ AccessRequestData: plugindata.AccessRequestData{ From 660c75d09e71245675c8986089d0327b9b01a76f Mon Sep 17 00:00:00 2001 From: Edward Dowling Date: Mon, 28 Apr 2025 16:45:36 +0100 Subject: [PATCH 7/7] Update msteams plugin tests --- integrations/access/msteams/testlib/message_parser.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/access/msteams/testlib/message_parser.go b/integrations/access/msteams/testlib/message_parser.go index d903ea68e3259..1c27649d75387 100644 --- a/integrations/access/msteams/testlib/message_parser.go +++ b/integrations/access/msteams/testlib/message_parser.go @@ -52,7 +52,7 @@ func (msg testTeamsMessage) checkStatusApproved(t *testing.T, reason string) { require.GreaterOrEqual(t, len(body), 3) require.Equal(t, "✅", body[1].Columns[0].Items[0].Text) require.Equal(t, "APPROVED", body[1].Columns[1].Items[0].Text) - require.Equal(t, reason, body[2].Facts[4].Value) + require.Equal(t, reason, body[2].Facts[5].Value) } func (msg testTeamsMessage) checkStatusDenied(t *testing.T, reason string) { @@ -61,7 +61,7 @@ func (msg testTeamsMessage) checkStatusDenied(t *testing.T, reason string) { require.GreaterOrEqual(t, len(body), 3) require.Equal(t, "❌", body[1].Columns[0].Items[0].Text) require.Equal(t, "DENIED", body[1].Columns[1].Items[0].Text) - require.Equal(t, reason, body[2].Facts[4].Value) + require.Equal(t, reason, body[2].Facts[5].Value) } func (msg testTeamsMessage) checkStatusExpired(t *testing.T) {