From 82dbabd4f925f7236d8456ee42cf92bc1e2990f6 Mon Sep 17 00:00:00 2001 From: Mauro Morales Date: Mon, 29 Apr 2024 22:20:14 +0200 Subject: [PATCH 1/3] Add UserLists Signed-off-by: Mauro Morales --- go.mod | 2 +- go.sum | 1 - pkg/users/users.go | 80 ++++++++++++++++++++++++++++++++++++++- pkg/users/users_darwin.go | 33 ++++++++++++---- pkg/users/users_linux.go | 46 ++++++++++++++++++---- pkg/users/users_test.go | 64 +++++++++++++++++++++++++++++++ 6 files changed, 207 insertions(+), 19 deletions(-) create mode 100644 pkg/users/users_test.go diff --git a/go.mod b/go.mod index cb8d6f4..8357bce 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/mauromorales/xpasswd -go 1.22.2 +go 1.20 require ( github.com/onsi/ginkgo/v2 v2.17.1 diff --git a/go.sum b/go.sum index ab2fd36..3ab3e8c 100644 --- a/go.sum +++ b/go.sum @@ -32,7 +32,6 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/users/users.go b/pkg/users/users.go index eb3aeb9..511b7e8 100644 --- a/pkg/users/users.go +++ b/pkg/users/users.go @@ -1,12 +1,16 @@ // Package users provides primitives for user information on a system. package users +import ( + "strconv" +) + // User is an interface that represents a user on a system type User interface { // UID returns the user's unique ID - UID() string + UID() (int, error) // GID returns the user's group ID - GID() string + GID() (int, error) // Username returns the user's username Username() string // HomeDir returns the user's home directory @@ -16,3 +20,75 @@ type User interface { // RealName returns the user's real name RealName() string } + +type CommonUser struct { + uid string + gid string + username string + homeDir string + shell string + realName string +} + +func (u CommonUser) UID() (int, error) { + return strconv.Atoi(u.uid) +} + +func (u CommonUser) GID() (int, error) { + return strconv.Atoi(u.gid) +} + +func (u CommonUser) Username() string { + return u.username +} + +func (u CommonUser) HomeDir() string { + return u.homeDir +} + +func (u CommonUser) Shell() string { + return u.shell +} + +func (u CommonUser) RealName() string { + return u.realName +} + +// UserList is an interface that represents a list of users +type UserList interface { + // Get returns a user from the list by username + Get(username string) User + // GetAll returns all users in the list + GetAll() ([]User, error) + GenerateUID() int + LastUID() int + SetPath(path string) + Load() error +} + +// CommonUserList is a common implementation of UserList +type CommonUserList struct { + users []User + lastUID int +} + +// Get checks if a user with the given username exists +func (list CommonUserList) Get(username string) User { + for _, user := range list.users { + if user.Username() == username { + return user + } + } + return nil +} + +func (list CommonUserList) LastUID() int { + return list.lastUID +} + +func (list CommonUserList) GenerateUID() int { + if len(list.users) == 0 { + return 0 + } + return list.lastUID + 1 +} diff --git a/pkg/users/users_darwin.go b/pkg/users/users_darwin.go index d51ff2d..608729f 100644 --- a/pkg/users/users_darwin.go +++ b/pkg/users/users_darwin.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "os/exec" + "strconv" "strings" ) @@ -16,12 +17,12 @@ type DarwinUser struct { userShell string } -func (u DarwinUser) UID() string { - return u.uniqueID +func (u DarwinUser) UID() (int, error) { + return strconv.Atoi(u.uniqueID) } -func (u DarwinUser) GID() string { - return u.primaryGroupID +func (u DarwinUser) GID() (int, error) { + return strconv.Atoi(u.primaryGroupID) } func (u DarwinUser) Username() string { @@ -40,9 +41,18 @@ func (u DarwinUser) RealName() string { return u.realName } -// List returns a list of users on a Darwin system -func List() ([]DarwinUser, error) { - users := make([]DarwinUser, 0) +func NewUserList() UserList { + return &DarwinUserList{} +} + +// DarwinUserList is a list of Linux users +type DarwinUserList struct { + CommonUserList +} + +// GetAll returns a list of users on a Darwin system +func (l DarwinUserList) GetAll() ([]User, error) { + users := make([]User, 0) output, err := execDSCL("-readall", "/Users", "UniqueID", "PrimaryGroupID", "RealName", "UserShell", "NFSHomeDirectory", "RecordName") if err != nil { @@ -54,8 +64,17 @@ func List() ([]DarwinUser, error) { for _, record := range records { user := parseRecord(record) users = append(users, user) + uid, err := user.UID() + if err != nil { + return users, fmt.Errorf("failed to convert UID to int: %w", err) + } + if uid > l.lastUID { + l.lastUID = uid + } } + l.users = users + return users, nil } diff --git a/pkg/users/users_linux.go b/pkg/users/users_linux.go index 057489f..eaae851 100644 --- a/pkg/users/users_linux.go +++ b/pkg/users/users_linux.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "strconv" "strings" ) @@ -17,12 +18,22 @@ type LinuxUser struct { interpreter string } -func (u LinuxUser) UID() string { - return u.uid +func NewUserList() UserList { + return &LinuxUserList{path: "/etc/passwd"} } -func (u LinuxUser) GID() string { - return u.gid +// LinuxUserList is a list of Linux users +type LinuxUserList struct { + CommonUserList + path string +} + +func (u LinuxUser) UID() (int, error) { + return strconv.Atoi(u.uid) +} + +func (u LinuxUser) GID() (int, error) { + return strconv.Atoi(u.gid) } func (u LinuxUser) Username() string { @@ -41,13 +52,21 @@ func (u LinuxUser) RealName() string { return u.nameOrComment } -// List returns a list of users on a Linux system -func List() ([]User, error) { +func (l *LinuxUserList) SetPath(path string) { + l.path = path +} + +func (l *LinuxUserList) Load() error { + _, err := l.GetAll() + return err +} + +// GetAll returns all users in the list +func (l *LinuxUserList) GetAll() ([]User, error) { users := make([]User, 0) - file, err := os.Open("/etc/passwd") + file, err := os.Open(l.path) if err != nil { - fmt.Println("Error opening the file:", err) return users, err } defer file.Close() @@ -77,6 +96,15 @@ func List() ([]User, error) { interpreter: parts[6], } users = append(users, user) + + uid, err := user.UID() + if err != nil { + return users, fmt.Errorf("failed to convert UID to int: %w", err) + } + + if uid > l.lastUID { + l.lastUID = uid + } } // Check if there were errors during scanning @@ -84,5 +112,7 @@ func List() ([]User, error) { return users, fmt.Errorf("error reading the file: %w", err) } + l.users = users + return users, nil } diff --git a/pkg/users/users_test.go b/pkg/users/users_test.go new file mode 100644 index 0000000..983f51f --- /dev/null +++ b/pkg/users/users_test.go @@ -0,0 +1,64 @@ +package users + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("users", func() { + Describe("Get", func() { + var list CommonUserList + user := CommonUser{uid: "0", gid: "0", username: "root", homeDir: "/root", shell: "/bin/bash", realName: "root"} + users := []User{user} + + Context("when the user is not present in the list", func() { + JustBeforeEach(func() { + list = CommonUserList{} + }) + + It("returns nil", func() { + got := list.Get("foobar") + Expect(got).To(BeNil()) + }) + }) + + Context("when the user is present", func() { + JustBeforeEach(func() { + list = CommonUserList{users: users} + }) + + It("returns the user", func() { + got := list.Get("root") + Expect(got).To(Equal(user)) + }) + }) + }) + + Describe("GenerateUID", func() { + var list CommonUserList + user := CommonUser{uid: "0", gid: "0", username: "root", homeDir: "/root", shell: "/bin/bash", realName: "root"} + users := []User{user} + + Context("when the list is empty", func() { + JustBeforeEach(func() { + list = CommonUserList{} + }) + + It("returns 0", func() { + got := list.GenerateUID() + Expect(got).To(Equal(0)) + }) + }) + + Context("when the list is not empty", func() { + JustBeforeEach(func() { + list = CommonUserList{users: users} + }) + + It("returns the next available UID", func() { + got := list.GenerateUID() + Expect(got).To(Equal(1)) + }) + }) + }) +}) From aef9b22841b2da671df7431a581dfab622b7d371 Mon Sep 17 00:00:00 2001 From: Mauro Morales Date: Mon, 29 Apr 2024 22:27:53 +0200 Subject: [PATCH 2/3] Darwin Signed-off-by: Mauro Morales --- pkg/users/users_darwin.go | 8 ++++++++ pkg/users/users_darwin_test.go | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/users/users_darwin.go b/pkg/users/users_darwin.go index 608729f..a4a655a 100644 --- a/pkg/users/users_darwin.go +++ b/pkg/users/users_darwin.go @@ -50,6 +50,14 @@ type DarwinUserList struct { CommonUserList } +func (l *DarwinUserList) SetPath(path string) { +} + +func (l DarwinUserList) Load() error { + _, err := l.GetAll() + return err +} + // GetAll returns a list of users on a Darwin system func (l DarwinUserList) GetAll() ([]User, error) { users := make([]User, 0) diff --git a/pkg/users/users_darwin_test.go b/pkg/users/users_darwin_test.go index f0bfc94..e8420df 100644 --- a/pkg/users/users_darwin_test.go +++ b/pkg/users/users_darwin_test.go @@ -18,8 +18,8 @@ var _ = Describe("ListDarwing", func() { UserShell: /bin/sh` got := parseRecord(rootRecord) - Expect(got.UID()).To(Equal("0")) - Expect(got.GID()).To(Equal("0")) + Expect(got.UID()).To(Equal(0)) + Expect(got.GID()).To(Equal(0)) Expect(got.HomeDir()).To(Equal("/var/root")) Expect(got.Shell()).To(Equal("/bin/sh")) Expect(got.Username()).To(Equal("root")) From ca1e1f60d1b9fcbcf169caf493471a3881153e00 Mon Sep 17 00:00:00 2001 From: Mauro Morales Date: Mon, 29 Apr 2024 22:30:25 +0200 Subject: [PATCH 3/3] No need for goreleaser on prs Signed-off-by: Mauro Morales --- .github/workflows/release.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6748ee7..330aa2f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,7 +1,6 @@ name: goreleaser on: - pull_request: push: tags: - '*'