ARM-software/golang-utils

View on GitHub
utils/platform/users.go

Summary

Maintainability
A
1 hr
Test Coverage
package platform

import (
    "context"
    "fmt"
    "os/user"

    "github.com/mitchellh/go-homedir"

    "github.com/ARM-software/golang-utils/utils/commonerrors"
    "github.com/ARM-software/golang-utils/utils/parallelisation"
)

// DefineUser adds a new user to the platform
func DefineUser(ctx context.Context, user *user.User, password string) (err error) {
    if user == nil {
        err = fmt.Errorf("%w: missing user information", commonerrors.ErrUndefined)
        return
    }
    username := user.Username
    if username == "" {
        username = user.Uid
    }
    err = AddUser(ctx, username, user.Name, password)
    return
}

// AddUser adds a new user to the platform
func AddUser(ctx context.Context, username, fullname, password string) error {
    err := parallelisation.DetermineContextError(ctx)
    if err != nil {
        return err
    }
    found, _ := HasUser(username)
    if found {
        return nil
    }
    return ConvertUserGroupError(addUser(ctx, username, fullname, password))
}

// DeleteUser removes a user from the platform when the user is specified using a `user.User` structure.
func DeleteUser(ctx context.Context, user *user.User) (err error) {
    if user == nil {
        return
    }
    username := user.Username
    if username == "" {
        username = user.Uid
    }
    err = RemoveUser(ctx, username)
    return
}

// RemoveUser removes a user from the platform when only the username is known.
func RemoveUser(ctx context.Context, username string) error {
    err := parallelisation.DetermineContextError(ctx)
    if err != nil {
        return err
    }
    return ConvertUserGroupError(removeUser(ctx, username))
}

// HasUser checks whether a user exists
func HasUser(username string) (found bool, err error) {
    user, err := user.Lookup(username)
    if err != nil {
        err = ConvertUserGroupError(err)
        if commonerrors.Any(err, commonerrors.ErrNotFound) {
            err = nil
        }
    }
    if user != nil {
        found = true
    }
    return
}

// GetUser returns information about a user and expands its home directory.
func GetUser(username string) (auser *user.User, err error) {
    auser, err = user.Lookup(username)
    if err != nil {
        err = ConvertUserGroupError(err)
        return
    }
    if auser == nil {
        err = fmt.Errorf("%w: missing user information", commonerrors.ErrUnexpected)
        return
    }
    home, err := fetchHomeDirectory(username, auser.HomeDir)
    if err != nil {
        return
    }
    auser.HomeDir = home
    return
}

// GetCurrentUser returns information about the current platform's user and expands its home directory.
func GetCurrentUser() (currentUser *user.User, err error) {
    currentUser, err = user.Current()
    if err != nil {
        err = ConvertUserGroupError(err)
        return
    }
    if currentUser == nil {
        err = fmt.Errorf("%w: missing user information", commonerrors.ErrUnexpected)
        return
    }
    home, err := homedir.Dir()
    if err != nil {
        err = fmt.Errorf("%w: could not retrieve information about current user's home directory: %v", commonerrors.ErrUnexpected, err.Error())
        return
    }
    currentUser.HomeDir = home
    return
}

// IsCurrentUserAnAdmin states whether the current user is a superuser or not.
func IsCurrentUserAnAdmin() (admin bool, err error) {
    cuser, err := user.Current()
    if err != nil {
        err = fmt.Errorf("%w: cannot fetch the current user: %v", commonerrors.ErrUnexpected, err.Error())
        return
    }
    admin, err = IsUserAdmin(cuser)
    return
}

// IsUserAdmin states whether the user is a superuser or not. Similar to IsAdmin but may use more checks.
func IsUserAdmin(user *user.User) (admin bool, err error) {
    if user == nil {
        err = fmt.Errorf("%w: missing user", commonerrors.ErrUndefined)
        return
    }
    admin, subErr := isUserAdmin(user)
    if subErr == nil {
        return
    }
    admin, err = IsAdmin(user.Username)
    err = ConvertUserGroupError(err)
    return
}

// IsAdmin states whether the user is a superuser or not.
func IsAdmin(username string) (admin bool, err error) {
    found, subErr := HasUser(username)
    if !found && subErr == nil {
        return
    }
    admin, err = isAdmin(username)
    err = ConvertUserGroupError(err)
    if err == nil {
        return
    }
    // Make more check if username is current user
    current, subErr := user.Current()
    if subErr != nil {
        return
    }
    if current.Username != username {
        return
    }
    admin, err = isCurrentAdmin()
    err = ConvertUserGroupError(err)
    return
}

// HasGroup checks whether a group exists
func HasGroup(groupName string) (found bool, err error) {
    group, err := user.LookupGroup(groupName)
    if err != nil {
        err = ConvertUserGroupError(err)
        if commonerrors.Any(err, commonerrors.ErrNotFound) {
            err = nil
        }
    }
    if group != nil {
        found = true
    }
    return
}

// AddGroup creates a group if not already existing.
func AddGroup(ctx context.Context, groupName string) error {
    err := parallelisation.DetermineContextError(ctx)
    if err != nil {
        return err
    }
    found, _ := HasGroup(groupName)
    if found {
        return nil
    }
    return ConvertUserGroupError(addGroup(ctx, groupName))
}

// RemoveGroup removes a group from the platform
func RemoveGroup(ctx context.Context, groupName string) error {
    err := parallelisation.DetermineContextError(ctx)
    if err != nil {
        return err
    }
    return ConvertUserGroupError(removeGroup(ctx, groupName))
}

// AssociateUserToGroup adds a user to a group.
func AssociateUserToGroup(ctx context.Context, username, groupName string) error {
    err := parallelisation.DetermineContextError(ctx)
    if err != nil {
        return err
    }
    found, err := HasGroup(groupName)
    if err != nil || !found {
        return fmt.Errorf("%w: the group does not seem to exist: %v", commonerrors.ErrNotFound, err.Error())
    }
    found, err = HasUser(username)
    if err != nil || !found {
        return fmt.Errorf("%w: the user does not seem to exist: %v", commonerrors.ErrNotFound, err.Error())
    }
    return ConvertUserGroupError(associateUserToGroup(ctx, username, groupName))
}

// DissociateUserFromGroup removes a user from a group.
func DissociateUserFromGroup(ctx context.Context, username, groupName string) error {
    err := parallelisation.DetermineContextError(ctx)
    if err != nil {
        return err
    }
    found, _ := HasGroup(groupName)
    if !found && err == nil {
        return nil
    }
    found, _ = HasUser(username)
    if !found && err == nil {
        return nil
    }
    return ConvertUserGroupError(dissociateUserFromGroup(ctx, username, groupName))
}

// ConvertUserGroupError converts errors related to users in common errors.
func ConvertUserGroupError(err error) error {
    if err == nil {
        return nil
    }
    err = commonerrors.ConvertContextError(err)
    switch {
    case commonerrors.Any(err, commonerrors.ErrTimeout, commonerrors.ErrCancelled, commonerrors.ErrUnknown, commonerrors.ErrUnexpected, commonerrors.ErrNotFound):
        return err
    case commonerrors.CorrespondTo(err, "unknown"):
        return fmt.Errorf("%w: %v", commonerrors.ErrNotFound, err.Error())
    }
    return fmt.Errorf("%w: %v", commonerrors.ErrUnknown, err.Error())
}