Skip to content

Commit

Permalink
Allow updating legal holds.
Browse files Browse the repository at this point in the history
Fixes #2
  • Loading branch information
grundleborg committed Dec 14, 2023
1 parent bfa1c4c commit 50e9330
Show file tree
Hide file tree
Showing 17 changed files with 1,319 additions and 164 deletions.
64 changes: 63 additions & 1 deletion server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const requestBodyMaxSizeBytes = 1024 * 1024 // 1MB

// ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world.
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
// All HTTP endpoints of this plugin require a logged in user.
// All HTTP endpoints of this plugin require a logged-in user.
userID := r.Header.Get("Mattermost-User-ID")
if userID == "" {
http.Error(w, "Not authorized", http.StatusUnauthorized)
Expand All @@ -35,6 +35,7 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req
router.HandleFunc("/api/v1/legalhold/list", p.listLegalHolds)
router.HandleFunc("/api/v1/legalhold/create", p.createLegalHold)
router.HandleFunc("/api/v1/legalhold/{legalhold_id:[A-Za-z0-9]+}/release", p.releaseLegalHold)
router.HandleFunc("/api/v1/legalhold/{legalhold_id:[A-Za-z0-9]+}/update", p.updateLegalHold)

p.router = router
p.router.ServeHTTP(w, r)
Expand Down Expand Up @@ -142,6 +143,67 @@ func (p *Plugin) releaseLegalHold(w http.ResponseWriter, r *http.Request) {
}
}

// updateLegalHold updates the properties of a LegalHold
func (p *Plugin) updateLegalHold(w http.ResponseWriter, r *http.Request) {
var updateLegalHold model.UpdateLegalHold
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, requestBodyMaxSizeBytes)).Decode(&updateLegalHold); err != nil {
http.Error(w, "failed to parse request body", http.StatusBadRequest)
p.Client.Log.Error(err.Error())
return
}

// Check that the LegalHold matches the ID in the URL parameter.
legalholdID, err := RequireLegalHoldID(r)
if err != nil {
http.Error(w, "failed to parse LegalHold ID", http.StatusBadRequest)
p.Client.Log.Error(err.Error())
return
}

if legalholdID != updateLegalHold.ID {
http.Error(w, "invalid LegalHold ID", http.StatusBadRequest)
p.Client.Log.Error(err.Error())
return
}

if err = updateLegalHold.IsValid(); err != nil {
http.Error(w, "LegalHold update data is not valid", http.StatusBadRequest)
p.Client.Log.Error(err.Error())
return
}

// Retrieve the legal hold we are modifying
originalLegalHold, err := p.KVStore.GetLegalHoldByID(legalholdID)
if err != nil {
http.Error(w, "failed to update legal hold", http.StatusInternalServerError)
p.Client.Log.Error(err.Error())
return
}

newLegalHold := originalLegalHold.DeepCopy()
newLegalHold.ApplyUpdates(updateLegalHold)

savedLegalHold, err := p.KVStore.UpdateLegalHold(newLegalHold, *originalLegalHold)
if err != nil {
http.Error(w, "failed to update legal hold", http.StatusInternalServerError)
p.Client.Log.Error(err.Error())
return
}

b, jsonErr := json.Marshal(savedLegalHold)
if jsonErr != nil {
http.Error(w, "Error encoding json", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
_, err = w.Write(b)
if err != nil {
p.API.LogError("failed to write http response", err.Error())
return
}
}

func RequireLegalHoldID(r *http.Request) (string, error) {
props := mux.Vars(r)

Expand Down
3 changes: 3 additions & 0 deletions server/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package main

// TODO: Implement me!
155 changes: 155 additions & 0 deletions server/model/index_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package model

import (
"reflect"
"testing"

"github.com/stretchr/testify/require"
)

func TestMerge(t *testing.T) {
oldIndex := LegalHoldIndex{
"user1": {
Username: "oldUser",
Email: "[email protected]",
Channels: []LegalHoldChannelMembership{
{"channel1", 1000, 2000},
{"channel3", 400, 4400},
},
},
"user2": {
Username: "user2",
Email: "[email protected]",
Channels: []LegalHoldChannelMembership{
{"channel1", 1500, 2500},
{"channel2", 3000, 4000},
},
},
}

newIndex := LegalHoldIndex{
"user1": {
Username: "newUser",
Email: "[email protected]",
Channels: []LegalHoldChannelMembership{
{"channel1", 1500, 2500},
{"channel2", 3000, 4000},
},
},
"user3": {
Username: "user3",
Email: "[email protected]",
Channels: []LegalHoldChannelMembership{
{"channel1", 1500, 2500},
{"channel2", 3000, 4000},
},
},
}

expectedIndexAfterMerge := LegalHoldIndex{
"user1": {
Username: "newUser",
Email: "[email protected]",
Channels: []LegalHoldChannelMembership{
{"channel1", 1000, 2500},
{"channel2", 3000, 4000},
{"channel3", 400, 4400},
},
},
"user2": {
Username: "user2",
Email: "[email protected]",
Channels: []LegalHoldChannelMembership{
{"channel1", 1500, 2500},
{"channel2", 3000, 4000},
},
},
"user3": {
Username: "user3",
Email: "[email protected]",
Channels: []LegalHoldChannelMembership{
{"channel1", 1500, 2500},
{"channel2", 3000, 4000},
},
},
}

oldIndex.Merge(&newIndex)

if !reflect.DeepEqual(oldIndex, expectedIndexAfterMerge) {
t.Fail()
}
}

func TestGetLegalHoldChannelMembership(t *testing.T) {
type args struct {
channelMemberships []LegalHoldChannelMembership
channelID string
}

tests := []struct {
name string
args args
wantLegalHoldChannelMembership LegalHoldChannelMembership
wantFound bool
}{
{
name: "Test Case 1: Membership exists",
args: args{
channelMemberships: []LegalHoldChannelMembership{
{ChannelID: "ch1"},
{ChannelID: "ch2"},
},
channelID: "ch1",
},
wantLegalHoldChannelMembership: LegalHoldChannelMembership{ChannelID: "ch1"},
wantFound: true,
},
{
name: "Test Case 2: Membership does not exist",
args: args{
channelMemberships: []LegalHoldChannelMembership{
{ChannelID: "ch1"},
{ChannelID: "ch2"},
},
channelID: "ch3",
},
wantLegalHoldChannelMembership: LegalHoldChannelMembership{},
wantFound: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotLegalHoldChannelMembership, gotFound := getLegalHoldChannelMembership(tt.args.channelMemberships, tt.args.channelID)
if gotLegalHoldChannelMembership != tt.wantLegalHoldChannelMembership {
t.Errorf("getLegalHoldChannelMembership() got = %v, want %v", gotLegalHoldChannelMembership, tt.wantLegalHoldChannelMembership)
}
if gotFound != tt.wantFound {
t.Errorf("getLegalHoldChannelMembership() got1 = %v, want %v", gotFound, tt.wantFound)
}
})
}
}

func TestLegalHoldChannelMembership_Combine(t *testing.T) {
// Initialize a new LegalHoldChannelMembership instance
lhcm1 := LegalHoldChannelMembership{
ChannelID: "testChannel1",
StartTime: 10,
EndTime: 20,
}

lhcm2 := LegalHoldChannelMembership{
ChannelID: "testChannel2",
StartTime: 5,
EndTime: 25,
}

// Combine the two instances
lhcmCombined := lhcm1.Combine(lhcm2)

require.Equal(t, lhcm1.ChannelID, lhcmCombined.ChannelID)
require.Equal(t, int64(5), lhcmCombined.StartTime)
require.Equal(t, int64(25), lhcmCombined.EndTime)
}
52 changes: 52 additions & 0 deletions server/model/legal_hold.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ type LegalHold struct {
ExecutionLength int64 `json:"execution_length"`
}

// DeepCopy creates a deep copy of the LegalHold.
func (lh *LegalHold) DeepCopy() LegalHold {
newLegalHold := LegalHold{
ID: lh.ID,
Name: lh.Name,
DisplayName: lh.DisplayName,
CreateAt: lh.CreateAt,
UpdateAt: lh.UpdateAt,
DeleteAt: lh.DeleteAt,
StartsAt: lh.StartsAt,
EndsAt: lh.EndsAt,
LastExecutionEndedAt: lh.LastExecutionEndedAt,
ExecutionLength: lh.ExecutionLength,
}

copy(lh.UserIDs, newLegalHold.UserIDs)

return newLegalHold
}

// IsValidForCreate checks whether the LegalHold contains data that is valid for
// creation. If it is not valid, it returns an error describing the validation
// failure. It does not guarantee that creation in the store will be successful,
Expand All @@ -42,6 +62,10 @@ func (lh *LegalHold) IsValidForCreate() error {
return errors.New("LegalHold name must be between 2 and 64 characters in length")
}

if len(lh.DisplayName) > 64 || len(lh.DisplayName) < 2 {
return errors.New("LegalHold display name must be between 2 and 64 characters in length")
}

// FIXME: More validation required here.

return nil
Expand Down Expand Up @@ -95,3 +119,31 @@ func NewLegalHoldFromCreate(lhc CreateLegalHold) LegalHold {
ExecutionLength: 86400000,
}
}

// UpdateLegalHold holds the data that is specified in teh API call to update a LegalHold.
type UpdateLegalHold struct {
ID string `json:"id"`
DisplayName string `json:"display_name"`
UserIDs []string `json:"user_ids"`
EndsAt int64 `json:"ends_at"`
}

func (ulh UpdateLegalHold) IsValid() error {
if !mattermostModel.IsValidId(ulh.ID) {
return errors.New(fmt.Sprintf("LegalHold ID is not valid: %s", ulh.ID))
}

if len(ulh.DisplayName) > 64 || len(ulh.DisplayName) < 2 {
return errors.New("LegalHold display name must be between 2 and 64 characters in length")
}

// FIXME: More validation required here.

return nil
}

func (lh *LegalHold) ApplyUpdates(updates UpdateLegalHold) {
lh.DisplayName = updates.DisplayName
lh.UserIDs = updates.UserIDs
lh.EndsAt = updates.EndsAt
}
17 changes: 17 additions & 0 deletions server/model/legal_hold_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,20 @@ func TestIsFinished(t *testing.T) {
})
}
}

func TestBasePath(t *testing.T) {
cases := []struct {
lh *LegalHold
expected string
}{
{&LegalHold{Name: "testhold", ID: "1"}, "legal_hold/testhold_(1)"},
{&LegalHold{Name: "anotherhold", ID: "2"}, "legal_hold/anotherhold_(2)"},
}

for _, tc := range cases {
result := tc.lh.BasePath()
if result != tc.expected {
t.Errorf("BasePath() = %s; expected %s", result, tc.expected)
}
}
}
8 changes: 8 additions & 0 deletions server/store/kvstore/legal_hold_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,11 @@ func TestKVStore_GetAllLegalHolds(t *testing.T) {
require.NoError(t, err)
require.Len(t, result, 0)
}

func TestKVStore_UpdateLegalHold(t *testing.T) {
// TODO: Implement me!
}

func TestKVStore_DeleteLegalHold(t *testing.T) {
// TODO: Implement me!
}
3 changes: 2 additions & 1 deletion server/store/sqlstore/legal_hold.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package sqlstore

import (
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/mattermost-plugin-legal-hold/server/model"
"github.com/pkg/errors"

"github.com/mattermost/mattermost-plugin-legal-hold/server/model"
)

// GetPostsBatch fetches a batch of posts from the channel specified by channelID for a legal
Expand Down
4 changes: 4 additions & 0 deletions server/store/sqlstore/legal_hold_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,7 @@ func TestSQLStore_LegalHold_GetChannelIDsForUserDuring(t *testing.T) {
require.NoError(t, err)
require.ElementsMatch(t, secondWindowChannelIDs, expectedTwo)
}

func TestSQLStore_LegalHold_GetFileInfosByIDs(t *testing.T) {
// TODO: Implement me!
}
Loading

0 comments on commit 50e9330

Please sign in to comment.