diff --git a/pkg/users/users.go b/pkg/users/users.go index 511b7e8..9c69c78 100644 --- a/pkg/users/users.go +++ b/pkg/users/users.go @@ -13,6 +13,8 @@ type User interface { GID() (int, error) // Username returns the user's username Username() string + // Password returns the user's password (this is usually not used but we include it for completeness) + Password() string // HomeDir returns the user's home directory HomeDir() string // Shell returns the user's shell @@ -25,6 +27,7 @@ type CommonUser struct { uid string gid string username string + password string homeDir string shell string realName string @@ -42,6 +45,10 @@ func (u CommonUser) Username() string { return u.username } +func (u CommonUser) Password() string { + return u.password +} + func (u CommonUser) HomeDir() string { return u.homeDir } diff --git a/pkg/users/users_darwin.go b/pkg/users/users_darwin.go index a4a655a..96dd46b 100644 --- a/pkg/users/users_darwin.go +++ b/pkg/users/users_darwin.go @@ -8,8 +8,10 @@ import ( "strings" ) +// DarwinUser matches the fields in ds type DarwinUser struct { recordName string + password string uniqueID string primaryGroupID string realName string @@ -29,6 +31,10 @@ func (u DarwinUser) Username() string { return u.recordName } +func (u DarwinUser) Password() string { + return u.password +} + func (u DarwinUser) HomeDir() string { return u.nFSHomeDirectory } @@ -62,7 +68,7 @@ func (l DarwinUserList) Load() error { func (l DarwinUserList) GetAll() ([]User, error) { users := make([]User, 0) - output, err := execDSCL("-readall", "/Users", "UniqueID", "PrimaryGroupID", "RealName", "UserShell", "NFSHomeDirectory", "RecordName") + output, err := execDSCL("-readall", "/Users", "UniqueID", "PrimaryGroupID", "RealName", "UserShell", "NFSHomeDirectory", "RecordName", "Password") if err != nil { return users, fmt.Errorf("failed to execute command: %w", err) } @@ -107,6 +113,8 @@ func parseRecord(record string) DarwinUser { switch key { case "RecordName": user.recordName = val + case "Password": + user.password = val case "UniqueID": user.uniqueID = val case "PrimaryGroupID": diff --git a/pkg/users/users_darwin_test.go b/pkg/users/users_darwin_test.go index e8420df..badadbe 100644 --- a/pkg/users/users_darwin_test.go +++ b/pkg/users/users_darwin_test.go @@ -8,6 +8,7 @@ import ( var _ = Describe("ListDarwing", func() { It("parse record", func() { rootRecord := `NFSHomeDirectory: /var/root + Password: * PrimaryGroupID: 0 RealName: System Administrator @@ -24,5 +25,40 @@ var _ = Describe("ListDarwing", func() { Expect(got.Shell()).To(Equal("/bin/sh")) Expect(got.Username()).To(Equal("root")) Expect(got.RealName()).To(Equal("System Administrator")) + Expect(got.Password()).To(Equal("*")) + }) +}) + +var _ = Describe("DarwinUser", func() { + Describe("Get", func() { + var list DarwinUserList + rootUser := DarwinUser{uniqueID: "0", primaryGroupID: "0", recordName: "root", password: "*", nFSHomeDirectory: "/root", userShell: "/bin/bash", realName: "root"} + barbazUser := DarwinUser{uniqueID: "1000", primaryGroupID: "1000", recordName: "barbaz", password: "*", nFSHomeDirectory: "/home/barbaz", userShell: "/bin/bash", realName: "Bar Baz"} + users := []User{rootUser, barbazUser} + + Context("when the user is not present in the list", func() { + JustBeforeEach(func() { + list = DarwinUserList{} + }) + + It("returns nil", func() { + got := list.Get("foobar") + Expect(got).To(BeNil()) + }) + }) + + Context("when the user is present", func() { + JustBeforeEach(func() { + list = DarwinUserList{ + CommonUserList{users: users, lastUID: 1000}, + } + }) + + It("returns the user", func() { + got := list.Get("root") + Expect(got).To(Equal(rootUser)) + Expect(list.GenerateUID()).To(Equal(1001)) + }) + }) }) }) diff --git a/pkg/users/users_linux.go b/pkg/users/users_linux.go index eaae851..2530c32 100644 --- a/pkg/users/users_linux.go +++ b/pkg/users/users_linux.go @@ -8,14 +8,15 @@ import ( "strings" ) +// LinuxUser matches the fields in /etc/passwd. See man 5 passwd for more information type LinuxUser struct { - login string - uid string - gid string - nameOrComment string - home string - shell string - interpreter string + login string + password string + uid string + gid string + userNameOrComment string + userHomeDir string + usercommandInterpreter string } func NewUserList() UserList { @@ -40,16 +41,20 @@ func (u LinuxUser) Username() string { return u.login } +func (u LinuxUser) Password() string { + return u.password +} + func (u LinuxUser) HomeDir() string { - return u.home + return u.userHomeDir } func (u LinuxUser) Shell() string { - return u.shell + return u.usercommandInterpreter } func (u LinuxUser) RealName() string { - return u.nameOrComment + return u.userNameOrComment } func (l *LinuxUserList) SetPath(path string) { @@ -78,23 +83,8 @@ func (l *LinuxUserList) GetAll() ([]User, error) { for scanner.Scan() { line := scanner.Text() - // Split the line into parts using ':' as the delimiter - parts := strings.Split(line, ":") + user, err := parseRecord(line) - // Check if the line is correctly formatted with 7 fields - if len(parts) != 7 { - return users, fmt.Errorf("unexpected format: %s", line) - } - - user := LinuxUser{ - login: parts[0], - uid: parts[2], - gid: parts[3], - nameOrComment: parts[4], - home: parts[5], - shell: parts[6], - interpreter: parts[6], - } users = append(users, user) uid, err := user.UID() @@ -116,3 +106,22 @@ func (l *LinuxUserList) GetAll() ([]User, error) { return users, nil } + +func parseRecord(record string) (LinuxUser, error) { + user := LinuxUser{} + fields := strings.Split(record, ":") + // Check if the line is correctly formatted with 7 fields + if len(fields) != 7 { + return user, fmt.Errorf("unexpected format: %s", record) + } + + user.login = fields[0] + user.password = fields[1] + user.uid = fields[2] + user.gid = fields[3] + user.userNameOrComment = fields[4] + user.userHomeDir = fields[5] + user.usercommandInterpreter = fields[6] + + return user, nil +} diff --git a/pkg/users/users_linux_test.go b/pkg/users/users_linux_test.go new file mode 100644 index 0000000..214166a --- /dev/null +++ b/pkg/users/users_linux_test.go @@ -0,0 +1,87 @@ +package users + +import ( + "log" + "os" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("LinuxUserList", func() { + It("parses a record", func() { + rootRecord := `root:x:0:0:root:/root:/bin/bash` + + got, err := parseRecord(rootRecord) + Expect(err).To(BeNil()) + + Expect(got.UID()).To(Equal(0)) + Expect(got.GID()).To(Equal(0)) + Expect(got.HomeDir()).To(Equal("/root")) + Expect(got.Shell()).To(Equal("/bin/bash")) + Expect(got.Username()).To(Equal("root")) + Expect(got.RealName()).To(Equal("root")) + Expect(got.Password()).To(Equal("x")) + }) + + It("Gets all users", func() { + file, err := os.CreateTemp("", "passwd") + if err != nil { + log.Fatal(err) + } + defer os.Remove(file.Name()) + + _, err = file.WriteString("root:x:0:0:root:/root:/bin/bash\n") + Expect(err).ToNot(HaveOccurred()) + _, err = file.WriteString("foo:x:1000:1000:foo:/home/foo:/bin/bash\n") + Expect(err).ToNot(HaveOccurred()) + + list := LinuxUserList{} + list.SetPath(file.Name()) + err = list.Load() + Expect(err).ToNot(HaveOccurred()) + + user := list.Get("root") + Expect(user).ToNot(BeNil()) + user = list.Get("foo") + Expect(user).ToNot(BeNil()) + user = list.Get("bar") + Expect(user).To(BeNil()) + Expect(list.GenerateUID()).To(Equal(1001)) + }) +}) + +var _ = Describe("DarwinUser", func() { + Describe("Get", func() { + var list LinuxUserList + rootUser := LinuxUser{uid: "0", gid: "0", login: "root", password: "*", userHomeDir: "/root", usercommandInterpreter: "/bin/bash", userNameOrComment: "root"} + barbazUser := LinuxUser{uid: "1000", gid: "1000", login: "barbaz", password: "*", userHomeDir: "/home/barbaz", usercommandInterpreter: "/bin/bash", userNameOrComment: "Bar Baz"} + users := []User{rootUser, barbazUser} + + Context("when the user is not present in the list", func() { + JustBeforeEach(func() { + list = LinuxUserList{} + }) + + It("returns nil", func() { + got := list.Get("foobar") + Expect(got).To(BeNil()) + }) + }) + + Context("when the user is present", func() { + JustBeforeEach(func() { + list = LinuxUserList{ + CommonUserList: CommonUserList{users: users, lastUID: 1000}, + path: "/etc/passwd", + } + }) + + It("returns the user", func() { + got := list.Get("root") + Expect(got).To(Equal(rootUser)) + Expect(list.GenerateUID()).To(Equal(1001)) + }) + }) + }) +}) diff --git a/pkg/users/users_test.go b/pkg/users/users_test.go index 983f51f..fd7a4f5 100644 --- a/pkg/users/users_test.go +++ b/pkg/users/users_test.go @@ -8,8 +8,9 @@ import ( 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} + rootUser := CommonUser{uid: "0", gid: "0", username: "root", homeDir: "/root", shell: "/bin/bash", realName: "root"} + barbazUser := CommonUser{uid: "1000", gid: "1000", username: "barbaz", homeDir: "/home/barbaz", shell: "/bin/bash", realName: "Bar Baz"} + users := []User{rootUser, barbazUser} Context("when the user is not present in the list", func() { JustBeforeEach(func() { @@ -29,7 +30,7 @@ var _ = Describe("users", func() { It("returns the user", func() { got := list.Get("root") - Expect(got).To(Equal(user)) + Expect(got).To(Equal(rootUser)) }) }) }) @@ -37,7 +38,8 @@ var _ = Describe("users", func() { Describe("GenerateUID", func() { var list CommonUserList user := CommonUser{uid: "0", gid: "0", username: "root", homeDir: "/root", shell: "/bin/bash", realName: "root"} - users := []User{user} + foobar := CommonUser{uid: "1000", gid: "1000", username: "foobar", homeDir: "/home/foobar", shell: "/bin/bash", realName: "foo bar"} + users := []User{user, foobar} Context("when the list is empty", func() { JustBeforeEach(func() { @@ -52,12 +54,12 @@ var _ = Describe("users", func() { Context("when the list is not empty", func() { JustBeforeEach(func() { - list = CommonUserList{users: users} + list = CommonUserList{users: users, lastUID: 1000} }) It("returns the next available UID", func() { got := list.GenerateUID() - Expect(got).To(Equal(1)) + Expect(got).To(Equal(1001)) }) }) })