From 5d352cca7acba2b178f5e33bf69e67abe8794f3a Mon Sep 17 00:00:00 2001 From: Dustin Strobel Date: Tue, 26 Dec 2023 23:54:55 +0100 Subject: [PATCH 1/6] feat: add local group member functions --- windows/local/local_client.go | 4 +- windows/local/local_groupmember.go | 163 +++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 windows/local/local_groupmember.go diff --git a/windows/local/local_client.go b/windows/local/local_client.go index e8752d1..b1a82d7 100644 --- a/windows/local/local_client.go +++ b/windows/local/local_client.go @@ -16,9 +16,9 @@ type LocalClient struct { parser parser.ParserInterface } -// typeType is an interface for local types. +// localType is an interface for local types. type localType interface { - Group | []Group | User | []User + Group | []Group | User | []User | GroupMember | []GroupMember } // NewLocalClient returns a new instance of the LocalClient. diff --git a/windows/local/local_groupmember.go b/windows/local/local_groupmember.go new file mode 100644 index 0000000..70d34dd --- /dev/null +++ b/windows/local/local_groupmember.go @@ -0,0 +1,163 @@ +package local + +import ( + "context" + "fmt" + "strings" +) + +// GroupMember represents a member of a local Windows group. +type GroupMember struct { + Name string `json:"Name"` + SID SID `json:"SID"` + ObjectClass string `json:"ObjectClass"` +} + +// GroupMemberParams contains the parameters required for working with local Windows group members. +type GroupMemberParams struct { + Name string + SID string + Member string +} + +// GroupMemberRead retrieves information about a specific member in a local Windows group. +func (c *LocalClient) GroupMemberRead(ctx context.Context, params GroupMemberParams) (GroupMember, error) { + // Declare GroupMember + var gm GroupMember + + // Assert needed parameters + if params.Name == "" && params.SID == "" { + return gm, fmt.Errorf("windows.local.GroupMemberRead: group member parameter 'Name' or 'SID' must be set") + } + + if params.Member == "" { + return gm, fmt.Errorf("windows.local.GroupMemberRead: group member parameter 'Member' must be set") + } + + // Base command + cmds := []string{"Get-LocalGroupMember"} + + // Add parameters + // Prefer SID over Name + if params.SID != "" { + cmds = append(cmds, fmt.Sprintf("-SID %s", params.SID)) + } else if params.Name != "" { + cmds = append(cmds, fmt.Sprintf("-Name '%s'", params.Name)) + } + + cmds = append(cmds, fmt.Sprintf("-Member '%s'", params.Member)) + cmds = append(cmds, "| ConvertTo-Json -Compress") + cmd := strings.Join(cmds, " ") + + // Run command + if err := localRun[GroupMember](ctx, c, cmd, &gm); err != nil { + return gm, fmt.Errorf("windows.local.GroupMemberRead: %s", err) + } + + return gm, nil +} + +// GroupMemberList returns a list of members for a specific local Windows group. +func (c *LocalClient) GroupMemberList(ctx context.Context, params GroupMemberParams) ([]GroupMember, error) { + // Declare slice of GroupMember + var gm []GroupMember + + // Assert needed parameters + if params.Name == "" && params.SID == "" { + return gm, fmt.Errorf("windows.local.GroupMemberList: group member parameter 'Name' or 'SID' must be set") + } + + // Base command + cmds := []string{"$gm=Get-LocalGroupMember"} + + // Add parameters + // Prefer SID over Name + if params.SID != "" { + cmds = append(cmds, fmt.Sprintf("-SID %s", params.SID)) + } else if params.Name != "" { + cmds = append(cmds, fmt.Sprintf("-Name '%s'", params.Name)) + } + + // Ensure that groups with a single group member is also printed as an array + cmds = append(cmds, ";if($gm.Count -eq 1){ConvertTo-Json @($gm) -Compress}else{ConvertTo-Json $gm -Compress}") + cmd := strings.Join(cmds, " ") + + // Run command + if err := localRun[[]GroupMember](ctx, c, cmd, &gm); err != nil { + return gm, fmt.Errorf("windows.local.GroupMemberList: %s", err) + } + + return gm, nil +} + +// GroupMemberCreate adds a new member to a local Windows group. +func (c *LocalClient) GroupMemberCreate(ctx context.Context, params GroupMemberParams) error { + // Satisfy the localType interface + var gm GroupMember + + // Assert needed parameters + if params.Name == "" && params.SID == "" { + return fmt.Errorf("windows.local.GroupMemberCreate: group member parameter 'Name' or 'SID' must be set") + } + + if params.Member == "" { + return fmt.Errorf("windows.local.GroupMemberCreate: group member parameter 'Member' must be set") + } + + // Base command + cmds := []string{"Add-LocalGroupMember"} + + // Add parameters + // Prefer SID over Name + if params.SID != "" { + cmds = append(cmds, fmt.Sprintf("-SID %s", params.SID)) + } else if params.Name != "" { + cmds = append(cmds, fmt.Sprintf("-Name '%s'", params.Name)) + } + + cmds = append(cmds, fmt.Sprintf("-Member '%s'", params.Member)) + cmd := strings.Join(cmds, " ") + + // Run command + if err := localRun[GroupMember](ctx, c, cmd, &gm); err != nil { + return fmt.Errorf("windows.local.GroupMemberCreate: %s", err) + } + + return nil +} + +// GroupMemberDelete removes a member from a local Windows group. +func (c *LocalClient) GroupMemberDelete(ctx context.Context, params GroupMemberParams) error { + // Satisfy the localType interface + var gm GroupMember + + // Assert needed parameters + if params.Name == "" && params.SID == "" { + return fmt.Errorf("windows.local.GroupMemberDelete: group member parameter 'Name' or 'SID' must be set") + } + + if params.Member == "" { + return fmt.Errorf("windows.local.GroupMemberDelete: group member parameter 'Member' must be set") + } + + // Base command + cmds := []string{"Remove-LocalGroupMember"} + + // Add parameters + // Prefer SID over Name + if params.SID != "" { + cmds = append(cmds, fmt.Sprintf("-SID %s", params.SID)) + } else if params.Name != "" { + cmds = append(cmds, fmt.Sprintf("-Name '%s'", params.Name)) + } + + cmds = append(cmds, fmt.Sprintf("-Member '%s'", params.Member)) + cmd := strings.Join(cmds, " ") + + // Run command + if err := localRun[GroupMember](ctx, c, cmd, &gm); err != nil { + return fmt.Errorf("windows.local.GroupMemberDelete: %s", err) + } + + return nil +} From f0151975d872e358f478c4bfff0f9536f207420d Mon Sep 17 00:00:00 2001 From: Dustin Strobel Date: Wed, 27 Dec 2023 00:36:04 +0100 Subject: [PATCH 2/6] style: move localType type constraint to top --- windows/local/local_client.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/windows/local/local_client.go b/windows/local/local_client.go index b1a82d7..6987ec7 100644 --- a/windows/local/local_client.go +++ b/windows/local/local_client.go @@ -10,17 +10,17 @@ import ( "github.com/d-strobel/gowindows/parser" ) +// localType is a type constraint for the localRun function, ensuring it works with specific types. +type localType interface { + Group | []Group | User | []User | GroupMember | []GroupMember +} + // LocalClient represents a client for handling local Windows functions. type LocalClient struct { Connection connection.ConnectionInterface parser parser.ParserInterface } -// localType is an interface for local types. -type localType interface { - Group | []Group | User | []User | GroupMember | []GroupMember -} - // NewLocalClient returns a new instance of the LocalClient. // It requires a connection and parser as input parameters. func NewLocalClient(conn *connection.Connection, parser *parser.Parser) *LocalClient { From 778cec3edb6f1bd80e9cc2facc61ff5cd4b70c4a Mon Sep 17 00:00:00 2001 From: Dustin Strobel Date: Wed, 27 Dec 2023 00:36:23 +0100 Subject: [PATCH 3/6] docs: change accepted params comment --- windows/local/local_group.go | 8 ++++---- windows/local/local_user.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/windows/local/local_group.go b/windows/local/local_group.go index 836e875..7e7fd17 100644 --- a/windows/local/local_group.go +++ b/windows/local/local_group.go @@ -29,7 +29,7 @@ type GroupParams struct { // GroupRead gets a local group by SID or Name and returns a Group object. // -// Accepted group parameters: +// Accepted GroupParams: // - Name // - SID func (c *LocalClient) GroupRead(ctx context.Context, params GroupParams) (Group, error) { @@ -88,7 +88,7 @@ func (c *LocalClient) GroupList(ctx context.Context) ([]Group, error) { // GroupCreate creates a new local group and returns the Group object. // -// Accepted group parameters: +// Accepted GroupParams: // - Name // - Description func (c *LocalClient) GroupCreate(ctx context.Context, params GroupParams) (Group, error) { @@ -124,7 +124,7 @@ func (c *LocalClient) GroupCreate(ctx context.Context, params GroupParams) (Grou // GroupUpdate updates a local group. // -// Accepted group parameters: +// Accepted GroupParams: // - Name // - SID // - Description @@ -162,7 +162,7 @@ func (c *LocalClient) GroupUpdate(ctx context.Context, params GroupParams) error // GroupDelete removes a local group by SID or Name. // -// Accepted group parameters: +// Accepted GroupParams: // - Name // - SID func (c *LocalClient) GroupDelete(ctx context.Context, params GroupParams) error { diff --git a/windows/local/local_user.go b/windows/local/local_user.go index f500b8d..169895b 100644 --- a/windows/local/local_user.go +++ b/windows/local/local_user.go @@ -63,7 +63,7 @@ type UserParams struct { // UserRead gets a local user by SID or Name and returns a User object. // -// Accepted user parameters: +// Accepted UserParams: // - Name // - SID func (c *LocalClient) UserRead(ctx context.Context, params UserParams) (User, error) { @@ -122,7 +122,7 @@ func (c *LocalClient) UserList(ctx context.Context) ([]User, error) { // UserCreate creates a local user and returns a User object. // -// Accepted user parameters: +// Accepted UserParams: // - Name // - Description // - AccountExpires @@ -197,7 +197,7 @@ func (c *LocalClient) UserCreate(ctx context.Context, params UserParams) (User, // UserUpdate updates a local user. // -// Accepted user parameters: +// Accepted UserParams: // - Name // - SID // - Description @@ -282,7 +282,7 @@ func (c *LocalClient) UserUpdate(ctx context.Context, params UserParams) error { // UserDelete removes a local user by SID or Name. // -// Accepted user parameters: +// Accepted UserParams: // - Name // - SID func (c *LocalClient) UserDelete(ctx context.Context, params UserParams) error { From 8b8ccc4ab9ffa7c4db25ad5d30ac0c6d1c07432b Mon Sep 17 00:00:00 2001 From: Dustin Strobel Date: Wed, 27 Dec 2023 00:36:37 +0100 Subject: [PATCH 4/6] docs: add accepted params comment --- windows/local/local_groupmember.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/windows/local/local_groupmember.go b/windows/local/local_groupmember.go index 70d34dd..d2e7eeb 100644 --- a/windows/local/local_groupmember.go +++ b/windows/local/local_groupmember.go @@ -21,6 +21,11 @@ type GroupMemberParams struct { } // GroupMemberRead retrieves information about a specific member in a local Windows group. +// +// Accepted GroupMemberParams: +// - Name +// - SID +// - Member func (c *LocalClient) GroupMemberRead(ctx context.Context, params GroupMemberParams) (GroupMember, error) { // Declare GroupMember var gm GroupMember @@ -58,6 +63,10 @@ func (c *LocalClient) GroupMemberRead(ctx context.Context, params GroupMemberPar } // GroupMemberList returns a list of members for a specific local Windows group. +// +// Accepted GroupMemberParams: +// - Name +// - SID func (c *LocalClient) GroupMemberList(ctx context.Context, params GroupMemberParams) ([]GroupMember, error) { // Declare slice of GroupMember var gm []GroupMember @@ -91,6 +100,11 @@ func (c *LocalClient) GroupMemberList(ctx context.Context, params GroupMemberPar } // GroupMemberCreate adds a new member to a local Windows group. +// +// Accepted GroupMemberParams: +// - Name +// - SID +// - Member func (c *LocalClient) GroupMemberCreate(ctx context.Context, params GroupMemberParams) error { // Satisfy the localType interface var gm GroupMember @@ -127,6 +141,11 @@ func (c *LocalClient) GroupMemberCreate(ctx context.Context, params GroupMemberP } // GroupMemberDelete removes a member from a local Windows group. +// +// Accepted GroupMemberParams: +// - Name +// - SID +// - Member func (c *LocalClient) GroupMemberDelete(ctx context.Context, params GroupMemberParams) error { // Satisfy the localType interface var gm GroupMember From 029ba0050869e23c6735bf16debd6b0ae2db162f Mon Sep 17 00:00:00 2001 From: Dustin Strobel Date: Wed, 27 Dec 2023 18:45:47 +0100 Subject: [PATCH 5/6] test: add unit tests for local.GroupMember functions --- windows/local/local_groupmember_test.go | 275 ++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 windows/local/local_groupmember_test.go diff --git a/windows/local/local_groupmember_test.go b/windows/local/local_groupmember_test.go new file mode 100644 index 0000000..d78a03c --- /dev/null +++ b/windows/local/local_groupmember_test.go @@ -0,0 +1,275 @@ +package local + +import ( + "context" + "errors" + + "github.com/d-strobel/gowindows/connection" + + mockConnection "github.com/d-strobel/gowindows/connection/mocks" + mockParser "github.com/d-strobel/gowindows/parser/mocks" +) + +// Fixtures +const ( + groupMemberRead = `{"Name":"WIN2022SC\\Administrator","SID":{"BinaryLength":28,"AccountDomainSid":{"BinaryLength":24,"AccountDomainSid":"S-1-5-21-153895498-367353507-3704405138","Value":"S-1-5-21-153895498-367353507-3704405138"},"Value":"S-1-5-21-153895498-367353507-3704405138-500"},"PrincipalSource":1,"ObjectClass":"User"}` + groupMemberList = `[{"Name":"WIN2022SC\\Administrator","SID":{"BinaryLength":28,"AccountDomainSid":"S-1-5-21-153895498-367353507-3704405138","Value":"S-1-5-21-153895498-367353507-3704405138-500"},"PrincipalSource":1,"ObjectClass":"User"},{"Name":"WIN2022SC\\vagrant","SID":{"BinaryLength":28,"AccountDomainSid":"S-1-5-21-153895498-367353507-3704405138","Value":"S-1-5-21-153895498-367353507-3704405138-1000"},"PrincipalSource":1,"ObjectClass":"User"}]` +) + +var ( + expectedGroupMemberRead = GroupMember{ + Name: "WIN2022SC\\Administrator", + SID: SID{ + Value: "S-1-5-21-153895498-367353507-3704405138-500", + }, + ObjectClass: "User", + } + expectedGroupMemberList = []GroupMember{ + { + Name: "WIN2022SC\\Administrator", + SID: SID{ + Value: "S-1-5-21-153895498-367353507-3704405138-500", + }, + ObjectClass: "User", + }, + { + Name: "WIN2022SC\\vagrant", + SID: SID{ + Value: "S-1-5-21-153895498-367353507-3704405138-1000", + }, + ObjectClass: "User", + }, + } +) + +func (suite *LocalUnitTestSuite) TestGroupMemberRead() { + + suite.Run("should return the correct GroupMember", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + mockConn := mockConnection.NewMockConnectionInterface(suite.T()) + mockParser := mockParser.NewMockParserInterface(suite.T()) + c := &LocalClient{ + Connection: mockConn, + parser: mockParser, + } + expectedCMD := "Get-LocalGroupMember -Name 'Administrators' -Member 'Administrator' | ConvertTo-Json -Compress" + mockConn.On("Run", ctx, expectedCMD).Return(connection.CMDResult{ + StdOut: groupMemberRead, + }, nil) + actualGroupMemberRead, err := c.GroupMemberRead(ctx, GroupMemberParams{Name: "Administrators", Member: "Administrator"}) + suite.Require().NoError(err) + mockConn.AssertCalled(suite.T(), "Run", ctx, expectedCMD) + mockParser.AssertNotCalled(suite.T(), "DecodeCLIXML") + suite.Equal(expectedGroupMemberRead, actualGroupMemberRead) + }) + + suite.Run("should run the correct command", func() { + tcs := []struct { + description string + inputParameters GroupMemberParams + expectedCMD string + }{ + { + "assert user by name", + GroupMemberParams{Name: "Administrators", Member: "Administrator"}, + "Get-LocalGroupMember -Name 'Administrators' -Member 'Administrator' | ConvertTo-Json -Compress", + }, + { + "assert users by sid", + GroupMemberParams{SID: "123456789", Member: "Test"}, + "Get-LocalGroupMember -SID 123456789 -Member 'Test' | ConvertTo-Json -Compress", + }, + { + "assert users by name and sid", + GroupMemberParams{Name: "Users", SID: "123456789", Member: "Test"}, + "Get-LocalGroupMember -SID 123456789 -Member 'Test' | ConvertTo-Json -Compress", + }, + } + + for _, tc := range tcs { + suite.T().Logf("test case: %s", tc.description) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + mockConn := mockConnection.NewMockConnectionInterface(suite.T()) + mockParser := mockParser.NewMockParserInterface(suite.T()) + c := &LocalClient{ + Connection: mockConn, + parser: mockParser, + } + mockConn.On("Run", ctx, tc.expectedCMD).Return(connection.CMDResult{}, nil) + _, err := c.GroupMemberRead(ctx, tc.inputParameters) + suite.Require().NoError(err) + mockConn.AssertCalled(suite.T(), "Run", ctx, tc.expectedCMD) + mockParser.AssertNotCalled(suite.T(), "DecodeCLIXML") + } + }) + + suite.Run("should return specific errors", func() { + tcs := []struct { + description string + inputParameters GroupMemberParams + expectedErr string + }{ + { + "assert error with empty parameters", + GroupMemberParams{}, + "windows.local.GroupMemberRead: group member parameter 'Name' or 'SID' must be set", + }, + { + "assert no member", + GroupMemberParams{Name: "Administrators"}, + "windows.local.GroupMemberRead: group member parameter 'Member' must be set", + }, + } + + for _, tc := range tcs { + suite.T().Logf("test case: %s", tc.description) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + mockConn := mockConnection.NewMockConnectionInterface(suite.T()) + mockParser := mockParser.NewMockParserInterface(suite.T()) + c := &LocalClient{ + Connection: mockConn, + parser: mockParser, + } + _, err := c.GroupMemberRead(ctx, tc.inputParameters) + suite.EqualError(err, tc.expectedErr) + mockConn.AssertNotCalled(suite.T(), "Run") + mockParser.AssertNotCalled(suite.T(), "DecodeCLIXML") + } + }) + + suite.Run("should return error if run fails", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + mockConn := mockConnection.NewMockConnectionInterface(suite.T()) + mockParser := mockParser.NewMockParserInterface(suite.T()) + c := &LocalClient{ + Connection: mockConn, + parser: mockParser, + } + expectedCMD := "Get-LocalGroupMember -Name 'Administrator' -Member 'Test' | ConvertTo-Json -Compress" + mockConn.On("Run", ctx, expectedCMD).Return(connection.CMDResult{}, errors.New("test-error")) + _, err := c.GroupMemberRead(ctx, GroupMemberParams{Name: "Administrator", Member: "Test"}) + suite.EqualError(err, "windows.local.GroupMemberRead: test-error") + mockConn.AssertCalled(suite.T(), "Run", ctx, expectedCMD) + mockParser.AssertNotCalled(suite.T(), "DecodeCLIXML") + }) +} + +func (suite *LocalUnitTestSuite) TestGroupMemberList() { + + suite.Run("should return the correct list of group member", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + mockConn := mockConnection.NewMockConnectionInterface(suite.T()) + mockParser := mockParser.NewMockParserInterface(suite.T()) + c := &LocalClient{ + Connection: mockConn, + parser: mockParser, + } + expectedCMD := "$gm=Get-LocalGroupMember -Name 'Administrators' ;if($gm.Count -eq 1){ConvertTo-Json @($gm) -Compress}else{ConvertTo-Json $gm -Compress}" + mockConn.On("Run", ctx, expectedCMD).Return(connection.CMDResult{ + StdOut: groupMemberList, + }, nil) + actualGroupMemberList, err := c.GroupMemberList(ctx, GroupMemberParams{Name: "Administrators"}) + suite.Require().NoError(err) + mockConn.AssertCalled(suite.T(), "Run", ctx, expectedCMD) + mockParser.AssertNotCalled(suite.T(), "DecodeCLIXML") + suite.Equal(expectedGroupMemberList, actualGroupMemberList) + }) + + suite.Run("should return error if run fails", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + mockConn := mockConnection.NewMockConnectionInterface(suite.T()) + mockParser := mockParser.NewMockParserInterface(suite.T()) + c := &LocalClient{ + Connection: mockConn, + parser: mockParser, + } + expectedCMD := "$gm=Get-LocalGroupMember -Name 'Administrators' ;if($gm.Count -eq 1){ConvertTo-Json @($gm) -Compress}else{ConvertTo-Json $gm -Compress}" + mockConn.On("Run", ctx, expectedCMD).Return(connection.CMDResult{}, errors.New("test-error")) + _, err := c.GroupMemberList(ctx, GroupMemberParams{Name: "Administrators"}) + suite.EqualError(err, "windows.local.GroupMemberList: test-error") + mockConn.AssertCalled(suite.T(), "Run", ctx, expectedCMD) + mockParser.AssertNotCalled(suite.T(), "DecodeCLIXML") + }) +} + +func (suite *LocalUnitTestSuite) TestGroupMemberCreate() { + suite.Run("should run the correct command", func() { + tcs := []struct { + description string + inputParameters GroupMemberParams + expectedCMD string + }{ + { + "assert user with Name + Member", + GroupMemberParams{Name: "Administrators", Member: "TestUser"}, + "Add-LocalGroupMember -Name 'Administrators' -Member 'TestUser'", + }, + { + "assert user with Name + SID + Member", + GroupMemberParams{Name: "Administrators", SID: "123456", Member: "TestUser"}, + "Add-LocalGroupMember -SID 123456 -Member 'TestUser'", + }, + } + + for _, tc := range tcs { + suite.T().Logf("test case: %s", tc.description) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + mockConn := mockConnection.NewMockConnectionInterface(suite.T()) + mockParser := mockParser.NewMockParserInterface(suite.T()) + c := &LocalClient{ + Connection: mockConn, + parser: mockParser, + } + mockConn.On("Run", ctx, tc.expectedCMD).Return(connection.CMDResult{}, nil) + err := c.GroupMemberCreate(ctx, tc.inputParameters) + suite.Require().NoError(err) + mockConn.AssertCalled(suite.T(), "Run", ctx, tc.expectedCMD) + mockParser.AssertNotCalled(suite.T(), "DecodeCLIXML") + } + }) +} + +func (suite *LocalUnitTestSuite) TestGroupMemberDelete() { + suite.Run("should run the correct command", func() { + tcs := []struct { + description string + inputParameters GroupMemberParams + expectedCMD string + }{ + { + "assert user with Name", + GroupMemberParams{Name: "Administrators", Member: "TestUser"}, + "Remove-LocalGroupMember -Name 'Administrators' -Member 'TestUser'", + }, + { + "assert user with Name + SID + Member", + GroupMemberParams{Name: "Administrators", SID: "123456", Member: "TestUser"}, + "Remove-LocalGroupMember -SID 123456 -Member 'TestUser'", + }, + } + + for _, tc := range tcs { + suite.T().Logf("test case: %s", tc.description) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + mockConn := mockConnection.NewMockConnectionInterface(suite.T()) + mockParser := mockParser.NewMockParserInterface(suite.T()) + c := &LocalClient{ + Connection: mockConn, + parser: mockParser, + } + mockConn.On("Run", ctx, tc.expectedCMD).Return(connection.CMDResult{}, nil) + err := c.GroupMemberDelete(ctx, tc.inputParameters) + suite.Require().NoError(err) + mockConn.AssertCalled(suite.T(), "Run", ctx, tc.expectedCMD) + mockParser.AssertNotCalled(suite.T(), "DecodeCLIXML") + } + }) +} From 3710ce97e718f98561539a0d725dbbd675f4d99a Mon Sep 17 00:00:00 2001 From: Dustin Strobel Date: Wed, 27 Dec 2023 19:32:25 +0100 Subject: [PATCH 6/6] test: add testacc for local.GroupMember functions --- windows/local/local_groupmember_acc_test.go | 89 +++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 windows/local/local_groupmember_acc_test.go diff --git a/windows/local/local_groupmember_acc_test.go b/windows/local/local_groupmember_acc_test.go new file mode 100644 index 0000000..ff00a1a --- /dev/null +++ b/windows/local/local_groupmember_acc_test.go @@ -0,0 +1,89 @@ +package local_test + +import ( + "context" + + "github.com/d-strobel/gowindows/windows/local" +) + +var groupMemberTestCases = []string{ + "Guest", + "DefaultAccount", +} + +// We insert numbers into the function names to ensure that +// the test functions for each local_* file run in a specific order. +func (suite *LocalAccTestSuite) TestGroupMember1Read() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + for _, c := range suite.clients { + params := local.GroupMemberParams{ + Name: "Administrators", + Member: "Administrator", + } + u, err := c.GroupMemberRead(ctx, params) + suite.Require().NoError(err) + suite.Equal(local.GroupMember{ + Name: "WIN2022SC\\Administrator", + SID: local.SID{ + Value: "S-1-5-21-153895498-367353507-3704405138-500", + }, + ObjectClass: "User", + }, u) + } +} + +func (suite *LocalAccTestSuite) TestGroupMember2List() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + for _, c := range suite.clients { + u, err := c.GroupMemberList(ctx, local.GroupMemberParams{ + Name: "Administrators", + }) + suite.Require().NoError(err) + suite.Contains(u, local.GroupMember{ + Name: "WIN2022SC\\Administrator", + SID: local.SID{ + Value: "S-1-5-21-153895498-367353507-3704405138-500", + }, + ObjectClass: "User", + }) + suite.Contains(u, local.GroupMember{ + Name: "WIN2022SC\\vagrant", + SID: local.SID{ + Value: "S-1-5-21-153895498-367353507-3704405138-1000", + }, + ObjectClass: "User", + }) + } +} + +func (suite *LocalAccTestSuite) TestGroupMember3Create() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + for i, c := range suite.clients { + params := local.GroupMemberParams{ + Name: "Administrators", + Member: groupMemberTestCases[i], + } + err := c.GroupMemberCreate(ctx, params) + suite.NoError(err) + } +} + +func (suite *LocalAccTestSuite) TestGroupMember4Remove() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + for i, c := range suite.clients { + params := local.GroupMemberParams{ + Name: "Administrators", + Member: groupMemberTestCases[i], + } + err := c.GroupMemberDelete(ctx, params) + suite.NoError(err) + } +}