Skip to content

Commit

Permalink
Merge pull request #30 from d-strobel/feat/local-groupmember-functions
Browse files Browse the repository at this point in the history
Feat/local-groupmember-functions
  • Loading branch information
d-strobel authored Dec 27, 2023
2 parents 14c0f17 + 3710ce9 commit 311e86b
Show file tree
Hide file tree
Showing 6 changed files with 559 additions and 13 deletions.
10 changes: 5 additions & 5 deletions windows/local/local_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

// typeType is an interface for local types.
type localType interface {
Group | []Group | User | []User
}

// 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 {
Expand Down
8 changes: 4 additions & 4 deletions windows/local/local_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
182 changes: 182 additions & 0 deletions windows/local/local_groupmember.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
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.
//
// Accepted GroupMemberParams:
// - Name
// - SID
// - Member
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.
//
// Accepted GroupMemberParams:
// - Name
// - SID
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.
//
// Accepted GroupMemberParams:
// - Name
// - SID
// - Member
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.
//
// Accepted GroupMemberParams:
// - Name
// - SID
// - Member
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
}
89 changes: 89 additions & 0 deletions windows/local/local_groupmember_acc_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit 311e86b

Please sign in to comment.