Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add primitives to work with groups #9

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.14

require (
github.com/go-ini/ini v1.37.0
github.com/google/uuid v1.2.0 // indirect
github.com/hashicorp/go-uuid v0.0.0-20180228145832-27454136f036 // indirect
github.com/jcmturner/gofork v0.0.0-20180107083740-2aebee971930 // indirect
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/go-ini/ini v1.37.0 h1:/FpMfveJbc7ExTTDgT5nL9Vw+aZdst/c2dOxC931U+M=
github.com/go-ini/ini v1.37.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-uuid v0.0.0-20180228145832-27454136f036 h1:d8T6WIONl4rMCPcQ/eY3uSz3+e4/GaoflKjXrWMex1U=
github.com/hashicorp/go-uuid v0.0.0-20180228145832-27454136f036/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/gofork v0.0.0-20180107083740-2aebee971930 h1:v4CYlQ+HeysPHsr2QFiEO60gKqnvn1xwvuKhhAhuEkk=
Expand Down
155 changes: 155 additions & 0 deletions group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright 2021 Ivan Ermilov. All rights reserved.
// Use of this source code is governed by a BSD style
// license that can be found in the LICENSE file.

// Package ipa is a Go client library for FreeIPA
package ipa

import (
"encoding/json"
"errors"
"regexp"
)

type GroupRecord struct {
Dn string `json:"dn"`
Cn []string `json:"cn"`
IpaUniqueId []string `json:"ipauniqueid"`
GidNumber []string `json:"gidnumber"`
ObjectClass []string `json:"objectclass"`
Users []string `json:"member_user"`
}

var ErrorGroupRecordNotInitialized = errors.New("group record is not initialized")

func (g *GroupRecord) GetUsers() ([]string, error) {
var users []string
if len(g.Cn) <= 0 {
return users, ErrorGroupRecordNotInitialized
}
users = g.Users
return users, nil
}

func (c *Client) GroupAdd(cn string) (*GroupRecord, error) {
var groupRec *GroupRecord

var options = map[string]interface{}{}

res, err := c.rpc("group_add", []string{cn}, options)
if err != nil {
return groupRec, err
}

err = json.Unmarshal(res.Result.Data, &groupRec)
if err != nil {
return groupRec, err
}

return groupRec, nil
}

func (c *Client) GroupDelete(cn string) error {
var options = map[string]interface{}{}

_, err := c.rpc("group_del", []string{cn}, options)
if err != nil {
return err
}

return nil
}

func (c *Client) GroupShow(cn string) (*GroupRecord, error) {
var groupRec *GroupRecord

var options = map[string]interface{}{
"no_members": false,
"raw": false,
"all": false,
"rights": false,
}

res, err := c.rpc("group_show", []string{cn}, options)
if err != nil {
return groupRec, err
}

err = json.Unmarshal(res.Result.Data, &groupRec)
if err != nil {
return groupRec, err
}

return groupRec, nil
}

func (c *Client) CheckGroupExist(cn string) (bool, error) {
_, err := c.GroupShow(cn)

if err != nil {
re := regexp.MustCompile(`group not found`)
isGroupNotFoundError := re.Match([]byte(err.Error()))
if isGroupNotFoundError {
return false, nil
} else {
return false, err
}

}

return true, nil
}

func (c *Client) AddUserToGroup(groupCn string, userUid string) (*GroupRecord, error) {
var groupRec *GroupRecord

var options = map[string]interface{}{
"no_members": false,
"raw": false,
"all": false,
"user": []string{userUid},
}

res, err := c.rpc("group_add_member", []string{groupCn}, options)
if err != nil {
return groupRec, err
}

err = json.Unmarshal(res.Result.Data, &groupRec)
if err != nil {
return groupRec, err
}

return groupRec, nil
}

func (c *Client) RemoveUserFromGroup(groupCn string, userUid string) error {
var options = map[string]interface{}{
"no_members": false,
"raw": false,
"all": false,
"user": []string{userUid},
}

_, err := c.rpc("group_remove_member", []string{groupCn}, options)
if err != nil {
return err
}

return nil
}

func (c *Client) CheckUserMemberOfGroup(userName, groupName string) (bool, error) {
group, err := c.GroupShow(groupName)
if err != nil {
return false, err
}

for _, u := range group.Users {
if u == userName {
return true, nil
}
}

return false, nil
}
128 changes: 128 additions & 0 deletions group_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package ipa

import (
"fmt"
"os"
"strconv"
"testing"

"github.com/hashicorp/go-uuid"
)

func setUp(users []string, c *Client) {
for _, u := range users {
c.UserAdd(
u,
"[email protected]",
"firstname",
"lastname",
"/home/test1",
"/bin/bash",
false,
)
}
}

func tearDown(users []string, c *Client) {
for _, u := range users {
err := c.UserDelete(u)
if err != nil {
fmt.Println(err)
}
}
}

func TestGroup(t *testing.T) {
host := os.Getenv("GOIPA_TEST_HOST")
realm := os.Getenv("GOIPA_TEST_REALM")
c := NewClient(host, realm)
user := os.Getenv("GOIPA_TEST_USER")
pass := os.Getenv("GOIPA_TEST_PASSWD")
err := c.RemoteLogin(user, pass)
if err != nil {
t.Error(err)
}

var users []string
numUsers := 5
// userPrefix should be lowercase, freeipa will make all ids lowercase
userPrefix := "testuser"
for i := 1; i <= numUsers; i++ {
user := userPrefix + strconv.Itoa(i*10000+1)
users = append(users, user)
}
setUp(users, c)
t.Log("Added users to freeipa", users)

groupName, err := uuid.GenerateUUID()
if err != nil {
t.Error(err)
}

t.Logf("Given group with a generated name: %s\n", groupName)

res, err := c.GroupAdd(groupName)
if err != nil {
t.Logf("Could not add new group %s to freeipa\n", groupName)
t.Error(err)
}
t.Logf("Added new group to freeipa: %+v\n", res)

group, err := c.GroupShow(groupName)
if err != nil {
t.Logf("Not able to to fetch group %s from freeipa server", groupName)
t.Error(err)
}
t.Logf("Was able to fetch group %s from freeipa server", groupName)
t.Logf("%+v\n", group)

for _, u := range users {
groupWithMembers, err := c.AddUserToGroup(groupName, u)
if err != nil {
t.Logf("Could not add user %s to group %s\n", u, groupName)
t.Error(err)
}
isMember, err := c.CheckUserMemberOfGroup(u, groupName)
if err != nil {
t.Error(err)
}
if !isMember {
t.Errorf("User %s was not added to group %s", u, groupName)
}
t.Logf("%+v\n", groupWithMembers)
t.Logf("Added user %s to group %s\n", u, groupName)
}

userToRemove := users[0]
err = c.RemoveUserFromGroup(groupName, userToRemove)
if err != nil {
t.Logf("Could not remove user %s from group %s", userToRemove, groupName)
t.Error(err)
}
t.Logf("Removed User %s from group %s", userToRemove, groupName)
isMember, _ := c.CheckUserMemberOfGroup(userToRemove, groupName)
if isMember {
t.Errorf("User %s was not removed from group %s", userToRemove, groupName)
}
t.Logf("Checked that user %s was removed from group %s", userToRemove, groupName)

err = c.GroupDelete(groupName)
if err != nil {
t.Logf("Could not delete group %s\n", groupName)
t.Error(err)
}
t.Logf("Deleted group %s from freeipa\n", groupName)

groupExist, err := c.CheckGroupExist(groupName)
if err != nil {
t.Error(err)
}
if groupExist {
t.Logf("Group %s was not removed\n", groupName)
t.Error(err)
}
t.Logf("Checked group %s deletion from freeipa\n", groupName)

tearDown(users, c)
t.Log("Deleted users from freeipa", users)
}
3 changes: 2 additions & 1 deletion otp.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package ipa

import (
"encoding/json"
"strconv"
"strings"
)

Expand Down Expand Up @@ -112,7 +113,7 @@ func (d *Digits) UnmarshalJSON(b []byte) error {
}

func (d *Digits) String() string {
return string(*d)
return strconv.Itoa(int(*d))
}

// Remove OTP token
Expand Down
39 changes: 34 additions & 5 deletions user.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
)

Expand All @@ -33,7 +34,7 @@ type UserRecord struct {
NSAccountLock bool `json:"nsaccountlock"`
HomeDir IpaString `json:"homedirectory"`
Email IpaString `json:"mail"`
Mobile IpaString `json:"mobile"`
TelephoneNumber IpaString `json:"telephonenumber"`
Shell IpaString `json:"loginshell"`
SudoRules IpaString `json:"memberofindirect_sudorule"`
HbacRules IpaString `json:"memberofindirect_hbacrule"`
Expand Down Expand Up @@ -116,11 +117,11 @@ func (c *Client) UpdateSSHPubKeys(uid string, keys []string) ([]string, error) {

// Update mobile number. Currently will store only a single number. Any
// existing numbers will be overwritten.
func (c *Client) UpdateMobileNumber(uid string, number string) error {
func (c *Client) UpdateTelephoneNumber(uid string, number string) error {
options := map[string]interface{}{
"no_members": false,
"mobile": []string{number},
"all": false}
"no_members": false,
"telephonenumber": []string{number},
"all": false}

_, err := c.rpc("user_mod", []string{uid}, options)

Expand Down Expand Up @@ -303,3 +304,31 @@ func (c *Client) UserAdd(uid, email, first, last, homedir, shell string, random

return &userRec, nil
}

func (c *Client) UserDelete(uid string) error {
var options = map[string]interface{}{
"continue": false}

_, err := c.rpc("user_del", []string{uid}, options)
if err != nil {
return err
}

return nil
}

func (c *Client) CheckUserExist(uid string) (bool, error) {
_, err := c.UserShow(uid)

if err != nil {
re := regexp.MustCompile(`user not found`)
isUserNotFoundError := re.Match([]byte(err.Error()))
if isUserNotFoundError {
return false, nil
} else {
return false, err
}
}

return true, nil
}
Loading