Skip to content

Commit

Permalink
Merge pull request #1 from mauromorales/add-user-lists
Browse files Browse the repository at this point in the history
Add UserLists
  • Loading branch information
mauromorales committed Apr 29, 2024
2 parents 2795e8a + ca1e1f6 commit a7c5c81
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 22 deletions.
1 change: 0 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: goreleaser

on:
pull_request:
push:
tags:
- '*'
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 0 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
80 changes: 78 additions & 2 deletions pkg/users/users.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}
41 changes: 34 additions & 7 deletions pkg/users/users_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"os/exec"
"strconv"
"strings"
)

Expand All @@ -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 {
Expand All @@ -40,9 +41,26 @@ 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
}

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)

output, err := execDSCL("-readall", "/Users", "UniqueID", "PrimaryGroupID", "RealName", "UserShell", "NFSHomeDirectory", "RecordName")
if err != nil {
Expand All @@ -54,8 +72,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
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/users/users_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
46 changes: 38 additions & 8 deletions pkg/users/users_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)

Expand All @@ -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 {
Expand All @@ -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()
Expand Down Expand Up @@ -77,12 +96,23 @@ 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
if err := scanner.Err(); err != nil {
return users, fmt.Errorf("error reading the file: %w", err)
}

l.users = users

return users, nil
}
64 changes: 64 additions & 0 deletions pkg/users/users_test.go
Original file line number Diff line number Diff line change
@@ -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))
})
})
})
})

0 comments on commit a7c5c81

Please sign in to comment.