From 2fe1307da1104af15034146a74b1c7e626c30a06 Mon Sep 17 00:00:00 2001 From: Ivan Ermilov Date: Sat, 19 Jun 2021 15:36:16 +0200 Subject: [PATCH 1/6] fix tests - default freeipa does not have mobile field, only telephonenumber --- otp.go | 3 ++- user.go | 10 +++++----- user_test.go | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/otp.go b/otp.go index 5acb5a9..c5d0e67 100644 --- a/otp.go +++ b/otp.go @@ -6,6 +6,7 @@ package ipa import ( "encoding/json" + "strconv" "strings" ) @@ -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 diff --git a/user.go b/user.go index 6a7762c..a18f83e 100644 --- a/user.go +++ b/user.go @@ -33,7 +33,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"` @@ -116,11 +116,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) diff --git a/user_test.go b/user_test.go index 01cf7e6..10eaf16 100644 --- a/user_test.go +++ b/user_test.go @@ -108,12 +108,12 @@ func TestUpdateMobile(t *testing.T) { user := os.Getenv("GOIPA_TEST_USER") c := newTestClientUserPassword() - err := c.UpdateMobileNumber(user, "") + err := c.UpdateTelephoneNumber(user, "") if err != nil { t.Error("Failed to remove existing mobile number") } - err = c.UpdateMobileNumber(user, "+9999999999") + err = c.UpdateTelephoneNumber(user, "+9999999999") if err != nil { t.Error(err) } @@ -123,7 +123,7 @@ func TestUpdateMobile(t *testing.T) { t.Error(err) } - if string(rec.Mobile) != "+9999999999" { + if string(rec.TelephoneNumber) != "+9999999999" { t.Errorf("Invalid mobile number") } } From 7e7007d776007634660ac1fcb8fdcdd3e00c1635 Mon Sep 17 00:00:00 2001 From: Ivan Ermilov Date: Sat, 19 Jun 2021 17:57:43 +0200 Subject: [PATCH 2/6] add UserDelete method --- user.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/user.go b/user.go index a18f83e..46a5ef8 100644 --- a/user.go +++ b/user.go @@ -303,3 +303,15 @@ 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 +} From df72f4fc5f8ec9d6f42f88badf2dacb2afcdb862 Mon Sep 17 00:00:00 2001 From: Ivan Ermilov Date: Sat, 19 Jun 2021 17:58:17 +0200 Subject: [PATCH 3/6] add primitives to work with freeipa groups, cover them with tests --- go.mod | 1 + go.sum | 2 + group.go | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++ group_test.go | 118 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 260 insertions(+) create mode 100644 group.go create mode 100644 group_test.go diff --git a/go.mod b/go.mod index 27d8360..e444f5d 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 2813f6c..1380e7a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/group.go b/group.go new file mode 100644 index 0000000..680547a --- /dev/null +++ b/group.go @@ -0,0 +1,139 @@ +// 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" + "fmt" +) + +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) getCn() (string, error) { + var cn string + if len(g.Cn) <= 0 { + return cn, ErrorGroupRecordNotInitialized + } + return g.Cn[0], 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) 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 + } + + fmt.Printf("%s\n", res.Result.Data) + + 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 +} diff --git a/group_test.go b/group_test.go new file mode 100644 index 0000000..3d32eb5 --- /dev/null +++ b/group_test.go @@ -0,0 +1,118 @@ +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, + "test1@example.com", + "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) + + tearDown(users, c) + t.Log("Deleted users from freeipa", users) +} From aa65e9a964bb1c5863c5465c6bb334bdd5ec571c Mon Sep 17 00:00:00 2001 From: Ivan Ermilov Date: Sat, 19 Jun 2021 18:41:23 +0200 Subject: [PATCH 4/6] add CheckGroupExist method --- group.go | 17 +++++++++++++++++ group_test.go | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/group.go b/group.go index 680547a..85c6a03 100644 --- a/group.go +++ b/group.go @@ -9,6 +9,7 @@ import ( "encoding/json" "errors" "fmt" + "regexp" ) type GroupRecord struct { @@ -82,6 +83,22 @@ func (c *Client) GroupShow(cn string) (*GroupRecord, error) { return groupRec, nil } +func (c *Client) CheckGroupExist(cn string) (bool, error) { + _, err := c.GroupShow(cn) + + re := regexp.MustCompile(`group not found`) + isGroupNotFoundError := re.Match([]byte(err.Error())) + if isGroupNotFoundError { + return false, nil + } + + if err != nil { + return false, err + } + + return true, nil +} + func (c *Client) AddUserToGroup(groupCn string, userUid string) (*GroupRecord, error) { var groupRec *GroupRecord diff --git a/group_test.go b/group_test.go index 3d32eb5..70575fc 100644 --- a/group_test.go +++ b/group_test.go @@ -113,6 +113,16 @@ func TestGroup(t *testing.T) { } 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) } From 936943b9c9986a008ded577be4b03496a277da57 Mon Sep 17 00:00:00 2001 From: Ivan Ermilov Date: Sat, 19 Jun 2021 19:41:21 +0200 Subject: [PATCH 5/6] fix logic for checking whether group exists, add getter to group users --- group.go | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/group.go b/group.go index 85c6a03..e0ff8db 100644 --- a/group.go +++ b/group.go @@ -8,7 +8,6 @@ package ipa import ( "encoding/json" "errors" - "fmt" "regexp" ) @@ -23,12 +22,13 @@ type GroupRecord struct { var ErrorGroupRecordNotInitialized = errors.New("group record is not initialized") -func (g *GroupRecord) getCn() (string, error) { - var cn string +func (g *GroupRecord) GetUsers() ([]string, error) { + var users []string if len(g.Cn) <= 0 { - return cn, ErrorGroupRecordNotInitialized + return users, ErrorGroupRecordNotInitialized } - return g.Cn[0], nil + users = g.Users + return users, nil } func (c *Client) GroupAdd(cn string) (*GroupRecord, error) { @@ -86,14 +86,15 @@ func (c *Client) GroupShow(cn string) (*GroupRecord, error) { func (c *Client) CheckGroupExist(cn string) (bool, error) { _, err := c.GroupShow(cn) - re := regexp.MustCompile(`group not found`) - isGroupNotFoundError := re.Match([]byte(err.Error())) - if isGroupNotFoundError { - return false, nil - } - if err != nil { - return false, err + re := regexp.MustCompile(`group not found`) + isGroupNotFoundError := re.Match([]byte(err.Error())) + if isGroupNotFoundError { + return false, nil + } else { + return false, err + } + } return true, nil @@ -114,8 +115,6 @@ func (c *Client) AddUserToGroup(groupCn string, userUid string) (*GroupRecord, e return groupRec, err } - fmt.Printf("%s\n", res.Result.Data) - err = json.Unmarshal(res.Result.Data, &groupRec) if err != nil { return groupRec, err From 8c4307eb596eba35b7bb300c5b275f3433054657 Mon Sep 17 00:00:00 2001 From: Ivan Ermilov Date: Sat, 19 Jun 2021 19:41:51 +0200 Subject: [PATCH 6/6] add CheckUserExist func --- user.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/user.go b/user.go index 46a5ef8..ee1d5f6 100644 --- a/user.go +++ b/user.go @@ -11,6 +11,7 @@ import ( "fmt" "net/http" "net/url" + "regexp" "strings" ) @@ -315,3 +316,19 @@ func (c *Client) UserDelete(uid string) error { 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 +}