Skip to content
Snippets Groups Projects
Commit 080d7ef1 authored by Theo's avatar Theo :troll:
Browse files

Fix group attributions

parent 51d7b894
No related branches found
No related tags found
No related merge requests found
......@@ -12,17 +12,16 @@ and build the binary:
cd useradm && go build -o useradm
if you want add it the go path use:
if you want, add it the go path use:
go install
## Usage
For each command given we want to know what we will interact with (user/group),
as well as the action (create/remove/etc). The command will be asking for
confirmation in commands where some change is made so you can preview and deny
if something is not as you wanted.
as well as the action (create/remove/etc). The CLI will ask for confirmation
in commands where some change is made so you can preview and denyif something
is not as you wanted.
Note 1: order of flags does not matter
......@@ -30,20 +29,20 @@ Note 2: most commands have the -y flag to skip the confirmation if you want
### Show
To show a user's info we can use:
To show a user's info you can use:
useradm user show [options]
in flags the command needs identifiers to make the search, such as:
the command needs identifiers to make the search, such as:
useradm user show -n pedro -r 2024 -g bcc -i
here we are searching for a user with name with pedro, grr with 2024, is part
of the group 'bcc' and -i is to make the search case insensitive.
here it is searching for a user with name with pedro, grr with 2024, is part of
the group 'bcc' and -i is to make the search case insensitive.
### Create
To create a user we can use:
To create a user you can use:
useradm user create [options]
......@@ -64,7 +63,7 @@ the auto generation will be explained in the section "Implementation"
### Remove
To remove a user, since the login name is guaranteed to be unique, we do:
To remove a user, since the login name is guaranteed to be unique, you can use:
useradm user remove [username]
......@@ -75,14 +74,16 @@ be removed from LDAP and Kerberos, their directories will end up at:
webdir: /home/contas_removidas/html/<Year of removal>/
webdir: /nobackup/contas_removidas/<Year of removal>/
You can modify these paths in `/cmd/user/create.go`
### Modify
To modify a user we can do:
To modify a user you can do:
useradm user mod [username]
after pressing return a temp file with the user config will be opened in an
editor (based on your terminal varible $EDITOR).
editor (based on your terminal varible `$EDITOR`).
After editing you can save and exit the file and the modifications will be read,
shown for confirmation and applied.
......@@ -112,7 +113,6 @@ TODO :<<<<<
TODO :&&&&&&&
## Implementation
To make this binary I used Go's cobra CLI module since it is common and I wanted
......@@ -154,13 +154,13 @@ not accepted so the function for it is a little different (PS: don't judge me)
### Validation
The old command `useradm.py` didn't really validate the entries given and had no
confirmation. Because of that and some intelligence misfortunes there were/are
confirmation. Because of that ~and some intelligence misfortunes~ there were/are
some users with messed up names, gecos, etc. So I made an effort on making a CLI
that minimizes those misfortunes.
### Login generation
`useradm.py` had 3 function to generate names that used a very wierd algorithm.
`useradm.py` had 3 functions to generate names that used a very wierd algorithm.
At first I just copied them, then I thought it was too long and repetitive so
I merged them into one (See commit e49eca75). It was VERY ugly, so Fernando K.
helped and rewrote/improved the algorithm to a more mantainable state, thanks :)
......@@ -200,9 +200,11 @@ and so on. 'last' would generate:
and so on...
You may use `/cmd/user/create_test.go` if you want a test for the algorithm.
Essentially we add one letter to each name part for each variance added. We also
remove any connectives (do, da, de, dos, von...) from the name. If there comes a
time a person appears with a new connective, update the formatName() function
time a person appears with a new connective, update the `formatName()` function
currently at `/cmd/user/create.go` to account for that :)
......
......@@ -50,6 +50,7 @@ func init() {
// required flags
CreateUserCmd.MarkFlagRequired("name")
CreateUserCmd.MarkFlagRequired("group")
// made it required, may be overkill...
CreateUserCmd.MarkFlagRequired("course")
CreateUserCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt")
......@@ -380,6 +381,25 @@ func addUserLDAP(u model.User) error {
return fmt.Errorf("Failed to add user %s to LDAP: %v", u.UID, err)
}
err = addUserGroupLDAP(l, u.UID, u.GID)
if err != nil {
return err
}
return nil
}
// adds a user to a group in LDAP
func addUserGroupLDAP(l *ldap.Conn, UID, GID string) error {
groupDN := fmt.Sprintf("cn=%s,ou=grupos,dc=c3local", GID)
modReq := ldap.NewModifyRequest(groupDN, nil)
modReq.Add("memberUid", []string{UID})
err := l.Modify(modReq)
if err != nil {
return fmt.Errorf("Failed to add user %s to group %s: %v", UID, GID, err)
}
return nil
}
......
package user
// TODO: fix group assignments
import (
"bufio"
"fmt"
......@@ -33,15 +32,25 @@ var ModCmd = &cobra.Command{
RunE: modifyUserFunc,
}
func init() {
ModCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt")
}
func modifyUserFunc(cmd *cobra.Command, args []string) error {
var users []model.User
var opts model.Opts
users, err := getUsers()
if err != nil {
return err
}
err = opts.RetrieveOpts(cmd)
if err != nil {
return err
}
login := args[0]
res := searchUser(users, false, login, "", "", "", "", "")
res := searchUser(users, false, true, login, "", "", "", "", "")
if len(res) != 1 {
err = fmt.Errorf("More than one user found")
return err
......@@ -83,11 +92,16 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error {
return err
}
oldGroup := curr.GID
req, err := genRequest(curr, changes)
if err != nil {
return err
}
fmt.Printf("%v\n\n", curr.ToString())
confirmationPrompt(opts.Confirm, "update")
l, err := connLDAP()
if err != nil {
return err
......@@ -98,6 +112,16 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Failed to update user attributes: %v", err)
}
if oldGroup != changes.Group {
if err := delUserFromGroupLDAP(l, curr.UID, curr.GID); err != nil {
return err
}
if err := addUserGroupLDAP(l, curr.UID, changes.Group); err != nil {
return err
}
}
if err := clearCache(); err != nil {
fmt.Printf(`Failed to reload cache!
all is ok but may take a while to apply the changes
......@@ -165,6 +189,7 @@ func genRequest(curr model.User, changes cfg) (*ldap.ModifyRequest, error) {
func applyChangesToUser(c model.User, n cfg) model.User {
c.GRR = n.GRR
c.GID = n.Group
c.Name = n.Name
c.Resp = n.Resp
c.Shell = n.Shell
......
......@@ -88,7 +88,7 @@ func locateUser(login string) (model.User, error) {
return u, fmt.Errorf("Failed to find login in LDAP database: %v", err)
}
filter := searchUser(users, false, login, "", "", "", "", "")
filter := searchUser(users, false, true, login, "", "", "", "", "")
if len(filter) != 1 {
return u, fmt.Errorf(`More than one user matched the login given.
search made: "useradm user show -l %v"`, login)
......@@ -125,29 +125,26 @@ func delUserLDAP(u model.User) error {
}
defer l.Close()
// first remove user from all groups
// search for all groups the user is a member of
searchReq := ldap.NewSearchRequest(
"ou=grupos,dc=c3local,dc=com",
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, 0, 0, false,
"(memberUid="+u.UID+")", // filter by UID membership
[]string{"dn"},
"(memberUid="+u.UID+")", // Filter by UID membership
[]string{"dn", "cn"},
nil,
)
// searches groups for target
groups, err := l.Search(searchReq)
if err != nil && !ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
return fmt.Errorf("Group members search failed: %v", err)
}
// removing from groups
// iterate and remove user from each group
for _, entry := range groups.Entries {
modReq := ldap.NewModifyRequest(entry.DN, nil)
modReq.Delete("memberUid", []string{u.UID})
if err := l.Modify(modReq); err != nil &&
!ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchAttribute) {
log.Printf("Warning: Failed to remove from group %s: %v", entry.DN, err)
groupName := entry.GetAttributeValue("cn")
if err := delUserFromGroupLDAP(l, u.UID, groupName); err != nil {
log.Printf("Warning: %v", err)
}
}
......@@ -155,7 +152,20 @@ func delUserLDAP(u model.User) error {
delReq := ldap.NewDelRequest(u.DN, nil)
if err := l.Del(delReq); err != nil &&
!ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
return fmt.Errorf("user deletion failed: %v", err)
return fmt.Errorf("User deletion failed: %v", err)
}
return nil
}
func delUserFromGroupLDAP(l *ldap.Conn, userUID, GID string) error {
groupDN := fmt.Sprintf("cn=%s,ou=grupos,dc=c3local", GID)
modReq := ldap.NewModifyRequest(groupDN, nil)
modReq.Delete("memberUid", []string{userUID})
err := l.Modify(modReq)
if err != nil && !ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchAttribute) {
return fmt.Errorf("Failed to remove user %s from group %s: %v", userUID, groupDN, err)
}
return nil
......
......@@ -31,7 +31,7 @@ func resetPass(cmd *cobra.Command, args []string) error {
}
login := args[0]
res := searchUser(users, false, login, "", "", "", "", "")
res := searchUser(users, false, true, login, "", "", "", "", "")
if len(res) != 1 {
return fmt.Errorf("More than one user found")
}
......
......@@ -36,6 +36,7 @@ func init() {
ShowCmd.MarkFlagsOneRequired("grr", "name", "login", "group", "homedir", "status")
ShowCmd.Flags().BoolP("ignore", "i", false, "Make the search case-insensitive")
ShowCmd.Flags().BoolP("exact", "e", false, "Make the matching be exact")
}
func searchUserFunc(cmd *cobra.Command, args []string) error {
......@@ -51,7 +52,7 @@ func searchUserFunc(cmd *cobra.Command, args []string) error {
return err
}
filtered := searchUser(users, o.Ignore, o.UID, o.GID,
filtered := searchUser(users, o.Ignore, o.Exact, o.UID, o.GID,
o.Name, o.GRR, o.Status, o.Homedir)
if len(filtered) == 0 {
......@@ -66,26 +67,25 @@ func searchUserFunc(cmd *cobra.Command, args []string) error {
return nil
}
func searchUser(users []model.User, ig bool, l, g, n, r, s, h string) []model.User {
func searchUser(users []model.User, ig, ex bool, l, g, n, r, s, h string) []model.User {
return Filter(users, func(u model.User) bool {
contains := func(src, substr string) bool {
matches := func(src, target string) bool {
if ig {
return strings.Contains( // normalize if set to ignore
strings.ToLower(src),
strings.ToLower(substr),
)
src, target = strings.ToLower(src), strings.ToLower(target)
}
return strings.Contains(src, substr) // normal search
if ex {
return src == target // exact match
}
return strings.Contains(src, target) // partial match
}
// Search
return (r == "" || contains(u.GRR, r)) &&
(n == "" || contains(u.Name, n)) &&
(l == "" || contains(u.UID, l)) &&
(g == "" || contains(u.GID, g)) &&
(s == "" || contains(u.Status, s)) &&
(h == "" || contains(u.Homedir, h))
// search
return (r == "" || matches(u.GRR, r)) &&
(n == "" || matches(u.Name, n)) &&
(l == "" || matches(u.UID, l)) &&
(g == "" || matches(u.GID, g)) &&
(s == "" || matches(u.Status, s)) &&
(h == "" || matches(u.Homedir, h))
})
}
......
......@@ -94,7 +94,7 @@ func validateUID(login string) error {
if err != nil {
return err
}
res := searchUser(users, false, login, "", "", "", "", "")
res := searchUser(users, false, true, login, "", "", "", "", "")
if len(res) != 0 {
return fmt.Errorf(`The informed Login already exists in LDAP database
Note: To search for the account use "useradm user show -l %s"`, login)
......
......@@ -22,6 +22,7 @@ type Opts struct {
Expiry string
Homedir string
Ignore bool
Exact bool
Block bool
Unblock bool
Confirm bool
......@@ -44,12 +45,13 @@ func (o *Opts) ToString() string {
Expiry %s
Homedir %s
Ignore %v
Exact %v
Block %v
Unblock %v
Confirm %v`,
o.GRR, o.GID, o.UID, o.Resp, o.Name, o.Nobkp, o.Ltype, o.Shell,
o.Passwd, o.Webdir, o.Status, o.Course, o.Expiry, o.Homedir, o.Ignore,
o.Block, o.Unblock, o.Confirm)
o.Exact, o.Block, o.Unblock, o.Confirm)
}
func (o *Opts) RetrieveOpts(cmd *cobra.Command) error {
......@@ -145,6 +147,11 @@ func (o *Opts) RetrieveOpts(cmd *cobra.Command) error {
return err
}
o.Exact, err = getFlagBool(cmd, "exact")
if err != nil {
return err
}
return nil
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment