Archived
add unit tests for various utility functions,
add integration test for LDAPClient, add aiutomatic openldap configuration for testing through make, add make targets for tests improve make targets for build/clean, update README with build and test instructions
This commit is contained in:
@@ -1,9 +1,23 @@
|
|||||||
build: clean
|
.PHONY: build test clean dev-init
|
||||||
CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/ .
|
|
||||||
|
|
||||||
test: clean
|
build: clean
|
||||||
go run .
|
@echo "======================== Building Binary ======================="
|
||||||
|
CGO_ENABLED=0 go build -ldflags="-s -w" -v -o dist/ .
|
||||||
|
|
||||||
|
tests: dev-reinit
|
||||||
|
@echo "======================== Running Tests ========================="
|
||||||
|
go test -v -cover -coverpkg=./app/ -coverprofile coverage ./test/
|
||||||
|
@echo "======================= Coverage Report ========================"
|
||||||
|
go tool cover -func=coverage
|
||||||
|
@rm -f coverage
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
@echo "======================== Cleaning Project ======================"
|
||||||
go clean
|
go clean
|
||||||
rm -f dist/*
|
rm -f dist/*
|
||||||
|
|
||||||
|
dev-init:
|
||||||
|
@cd scripts; make dev-init
|
||||||
|
|
||||||
|
dev-reinit:
|
||||||
|
@cd scripts; make dev-reinit
|
||||||
@@ -33,4 +33,21 @@ ProxmoxAAS LDAP provides a simple API for managing users and groups in a simplif
|
|||||||
- ldapURL: url to the ldap server ie. `ldap://ldap.domain.net`
|
- ldapURL: url to the ldap server ie. `ldap://ldap.domain.net`
|
||||||
- baseDN: base DN ie. `dc=domain,dc=net`
|
- baseDN: base DN ie. `dc=domain,dc=net`
|
||||||
- sessionSecretKey: random value used to randomize cookie values, replace with any sufficiently large random string
|
- sessionSecretKey: random value used to randomize cookie values, replace with any sufficiently large random string
|
||||||
3. Run the binary
|
3. Run the binary
|
||||||
|
|
||||||
|
## Building and Testing from Source
|
||||||
|
|
||||||
|
Building requires the go toolchain. Testing requires the go toolchain, make, and apt. Currently only supports Debian.
|
||||||
|
|
||||||
|
### Building from Source
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
2. Run `go get` to get requirements
|
||||||
|
3. Run `make` to build the binary
|
||||||
|
|
||||||
|
### Testing Source
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
2. Run `go get` to get requirements
|
||||||
|
3. Run `make dev-init` to install test requirements including openldap (slapd), ldap-utils, debconf-utils
|
||||||
|
4. Run `make tests` to run all tests
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.PHONY: dev-init
|
||||||
|
|
||||||
|
prerequisites:
|
||||||
|
@echo "=================== Installing Prerequisites ==================="
|
||||||
|
apt install debconf-utils slapd ldap-utils sudo gettext
|
||||||
|
git clone https://git.tronnet.net/tronnet/open-ldap-setup
|
||||||
|
cd open-ldap-setup/; bash gencert.sh < ../gencert.conf;
|
||||||
|
rm -rf open-ldap-setup/
|
||||||
|
|
||||||
|
dev-init: prerequisites dev-reinit
|
||||||
|
|
||||||
|
dev-reinit:
|
||||||
|
@echo "====================== Initializing Slapd ======================"
|
||||||
|
cat debconf-slapd.conf | debconf-set-selections
|
||||||
|
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure slapd
|
||||||
|
git clone https://git.tronnet.net/tronnet/open-ldap-setup
|
||||||
|
cd open-ldap-setup/; bash setup.sh < ../setup.conf;
|
||||||
|
rm -rf open-ldap-setup/
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
slapd slapd/password1 password admin
|
||||||
|
slapd slapd/internal/adminpw password admin
|
||||||
|
slapd slapd/internal/generated_adminpw password admin
|
||||||
|
slapd slapd/password2 password admin
|
||||||
|
slapd slapd/unsafe_selfwrite_acl note
|
||||||
|
slapd slapd/purge_database boolean true
|
||||||
|
slapd slapd/domain string test.paasldap
|
||||||
|
slapd slapd/ppolicy_schema_needs_update select abort installation
|
||||||
|
slapd slapd/invalid_config boolean true
|
||||||
|
slapd slapd/move_old_database boolean true
|
||||||
|
slapd slapd/backend select MDB
|
||||||
|
slapd shared/organization string paasldap
|
||||||
|
slapd slapd/dump_database_destdir string /var/backups/slapd-VERSION
|
||||||
|
slapd slapd/no_configuration boolean false
|
||||||
|
slapd slapd/dump_database select when needed
|
||||||
|
slapd slapd/password_mismatch note
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
paasldap
|
||||||
|
localhost
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
dc=test,dc=paasldap
|
||||||
|
adminuser
|
||||||
|
adminuser@test.paasldap
|
||||||
|
admin
|
||||||
|
user
|
||||||
|
admin123
|
||||||
|
admin123
|
||||||
|
/etc/ssl/certs/ldap-ca-cert.pem
|
||||||
|
/etc/ldap/ldap-server-cert.pem
|
||||||
|
/etc/ldap/ldap-server-key.pem
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{,,}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"listenPort": 80,
|
||||||
|
"ldapURL": "ldap://localhost",
|
||||||
|
"startTLS": true,
|
||||||
|
"basedn": "dc=test,dc=paasldap",
|
||||||
|
"sessionSecretKey": "test",
|
||||||
|
"sessionCookieName": "PAASLDAPAuthTicket",
|
||||||
|
"sessionCookie": {
|
||||||
|
"path": "/",
|
||||||
|
"httpOnly": true,
|
||||||
|
"secure": false,
|
||||||
|
"maxAge": 7200
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"proxmoxaas-ldap/app"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RandInt(min int, max int) int {
|
||||||
|
return rand.Intn(max+1-min) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandString(n int) string {
|
||||||
|
var letters = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
|
b := make([]rune, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letters[rand.Intn(len(letters))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// random ldap style DN
|
||||||
|
func RandDN(n int) string {
|
||||||
|
return fmt.Sprintf("cn=%s,ou=%s,dc=%s,dc=%s", RandString(n), RandString(n), RandString(n), RandString(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// typically for testing values of a variable
|
||||||
|
func AssertEquals[T comparable](t *testing.T, label string, a T, b T) {
|
||||||
|
t.Helper()
|
||||||
|
if a != b {
|
||||||
|
t.Errorf(`%s = %#v; expected %#v.`, label, a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// asserting the success or failure of a generic error
|
||||||
|
func AssertError(t *testing.T, label string, gotErr error, expectErr error) {
|
||||||
|
t.Helper()
|
||||||
|
if gotErr != nil && expectErr != nil {
|
||||||
|
if gotErr.Error() != expectErr.Error() {
|
||||||
|
t.Errorf(`%s returned %s; expected %s`, label, gotErr.Error(), expectErr.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if gotErr != expectErr {
|
||||||
|
t.Errorf(`%s returned %s; expected %s`, label, gotErr.Error(), expectErr.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// typically for asserting the success or failure of an ldap result
|
||||||
|
func AssertLDAPError(t *testing.T, label string, gotErr any, expectErrCode uint16) {
|
||||||
|
t.Helper()
|
||||||
|
expectError := ldap.LDAPResultCodeMap[expectErrCode]
|
||||||
|
if expectErrCode == ldap.LDAPResultSuccess { // expect success
|
||||||
|
if gotErr != nil { // got an error
|
||||||
|
gotErr := gotErr.(error)
|
||||||
|
t.Errorf(`%s returned %s; expected %s.`, label, gotErr.Error(), "success")
|
||||||
|
} // did not get an error
|
||||||
|
} else { // expect error
|
||||||
|
if gotErr == nil { // did not get an error
|
||||||
|
t.Errorf(`%s returned %s; expected %s.`, label, "success", expectError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotErr := gotErr.(error)
|
||||||
|
if !ldap.IsErrorWithCode(gotErr, expectErrCode) { // got an error that does not match the expected error
|
||||||
|
t.Errorf(`%s returned %s; expected %s.`, label, gotErr.Error(), expectError)
|
||||||
|
} // got the expected error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// typically for asserting the success or failure of an http result
|
||||||
|
func AssertStatus(t *testing.T, label string, gotCode int, expectCode int) {
|
||||||
|
t.Helper()
|
||||||
|
if expectCode == http.StatusOK {
|
||||||
|
if gotCode != http.StatusOK { // got an error
|
||||||
|
t.Errorf(`%s returned %d; expected %d.`, label, gotCode, expectCode)
|
||||||
|
}
|
||||||
|
} else { // expect error
|
||||||
|
if gotCode == http.StatusOK { // did not get an error
|
||||||
|
t.Errorf(`%s returned %d; expected %d.`, label, gotCode, expectCode)
|
||||||
|
} else if gotCode != expectCode { // got an error that does not match the expected error
|
||||||
|
t.Errorf(`%s returned %d; expected %d.`, label, gotCode, expectCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare if two users are equal, accepts LDAPUser or gin.H
|
||||||
|
func AssertLDAPUserEquals(t *testing.T, label string, a any, b app.LDAPUser) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
aObj, ok := a.(app.LDAPUser)
|
||||||
|
if ok {
|
||||||
|
if !reflect.DeepEqual(aObj, b) {
|
||||||
|
t.Errorf(`%s = %#v; expected %#v.`, label, aObj, b)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aGin, ok := a.(gin.H)
|
||||||
|
if ok {
|
||||||
|
bGin := app.LDAPUserToGin(b)
|
||||||
|
if !reflect.DeepEqual(aGin, bGin) {
|
||||||
|
t.Errorf(`%s = %#v; expected %#v.`, label, aGin, bGin)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// not a supported type
|
||||||
|
t.Errorf(`%s = %#v; expected %#v.`, label, a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare if two users are equal, accepts LDAPUser or gin.H
|
||||||
|
func AssertLDAPGroupEquals(t *testing.T, label string, a any, b app.LDAPGroup) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
aObj, ok := a.(app.LDAPGroup)
|
||||||
|
if ok {
|
||||||
|
if !reflect.DeepEqual(aObj, b) {
|
||||||
|
t.Errorf(`%s = %#v; expected %#v.`, label, aObj, b)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aGin, ok := a.(gin.H)
|
||||||
|
if ok {
|
||||||
|
bGin := app.LDAPGroupToGin(b)
|
||||||
|
if !reflect.DeepEqual(aGin, bGin) {
|
||||||
|
t.Errorf(`%s = %#v; expected %#v.`, label, aGin, bGin)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// not a supported type
|
||||||
|
t.Errorf(`%s = %#v; expected %#v.`, label, a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _config, _ = app.GetConfig("test_config.json")
|
||||||
|
var BaseDN = _config.BaseDN
|
||||||
|
var PeopleDN = fmt.Sprintf("ou=people,%s", BaseDN)
|
||||||
|
var GroupDN = fmt.Sprintf("ou=groups,%s", BaseDN)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
userObj app.LDAPUser
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
groupname string
|
||||||
|
groupObj app.LDAPGroup
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
app "proxmoxaas-ldap/app"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// test the GetConfig utility function because it used in other tests
|
||||||
|
func TestConfig_ValidPath(t *testing.T) {
|
||||||
|
config, err := app.GetConfig("test_config.json")
|
||||||
|
|
||||||
|
AssertError(t, "GetConfig()", err, nil)
|
||||||
|
AssertEquals(t, "config.ListenPort", config.ListenPort, 80)
|
||||||
|
AssertEquals(t, "config.LdapURL", config.LdapURL, "ldap://localhost")
|
||||||
|
AssertEquals(t, "config.BaseDN", config.BaseDN, "dc=test,dc=paasldap")
|
||||||
|
AssertEquals(t, "config.SessionSecretKey", config.SessionSecretKey, "test")
|
||||||
|
AssertEquals(t, "config.SessionCookieName", config.SessionCookieName, "PAASLDAPAuthTicket")
|
||||||
|
AssertEquals(t, "config.SessionCookie.Path", config.SessionCookie.Path, "/")
|
||||||
|
AssertEquals(t, "config.SessionCookie.HttpOnly", config.SessionCookie.HttpOnly, true)
|
||||||
|
AssertEquals(t, "config.SessionCookie.Secure", config.SessionCookie.Secure, false)
|
||||||
|
AssertEquals(t, "config.SessionCookie.MaxAge", config.SessionCookie.MaxAge, 7200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_InvalidPath(t *testing.T) {
|
||||||
|
badFileName := RandString(16)
|
||||||
|
_, err := app.GetConfig(badFileName)
|
||||||
|
expectedErr := fmt.Errorf("open %s: no such file or directory", badFileName)
|
||||||
|
AssertError(t, "GetConfig()", err, expectedErr)
|
||||||
|
|
||||||
|
_, err = app.GetConfig("bad_config.json")
|
||||||
|
expectedErr = fmt.Errorf("invalid character ',' looking for beginning of object key string")
|
||||||
|
AssertError(t, "GetConfig()", err, expectedErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test the LDAPEntryToUser and LDAPUserToGin utility functions
|
||||||
|
func TestLDAPUserDataPipeline(t *testing.T) {
|
||||||
|
var memberOf []string
|
||||||
|
for i := 0; i < RandInt(5, 20); i++ {
|
||||||
|
memberOf = append(memberOf, RandDN(16))
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedUser := app.LDAPUser{
|
||||||
|
DN: RandDN(16),
|
||||||
|
Attributes: app.LDAPUserAttributes{
|
||||||
|
CN: RandString(16),
|
||||||
|
SN: RandString(16),
|
||||||
|
Mail: RandString(16),
|
||||||
|
UID: RandString(16),
|
||||||
|
MemberOf: memberOf,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes := make(map[string][]string)
|
||||||
|
attributes["cn"] = []string{expectedUser.Attributes.CN}
|
||||||
|
attributes["sn"] = []string{expectedUser.Attributes.SN}
|
||||||
|
attributes["mail"] = []string{expectedUser.Attributes.Mail}
|
||||||
|
attributes["uid"] = []string{expectedUser.Attributes.UID}
|
||||||
|
attributes["memberOf"] = expectedUser.Attributes.MemberOf
|
||||||
|
|
||||||
|
entry := ldap.NewEntry(expectedUser.DN, attributes)
|
||||||
|
|
||||||
|
user := app.LDAPEntryToLDAPUser(entry)
|
||||||
|
AssertLDAPUserEquals(t, "LDAPEntryToLDAPUser(entry) -> user", user, expectedUser)
|
||||||
|
|
||||||
|
json := app.LDAPUserToGin(user)
|
||||||
|
AssertLDAPUserEquals(t, "LDAPUserToGin(user) -> json", json, expectedUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test the LDAPEntryToGroup and LDAPGroupToGin utility functions
|
||||||
|
func TestLDAPGroupDataPipeline(t *testing.T) {
|
||||||
|
var member []string
|
||||||
|
for i := 0; i < RandInt(5, 20); i++ {
|
||||||
|
member = append(member, RandDN(16))
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedGroup := app.LDAPGroup{
|
||||||
|
DN: RandDN(16),
|
||||||
|
Attributes: app.LDAPGroupAttributes{
|
||||||
|
Member: member,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes := make(map[string][]string)
|
||||||
|
attributes["member"] = expectedGroup.Attributes.Member
|
||||||
|
|
||||||
|
entry := ldap.NewEntry(expectedGroup.DN, attributes)
|
||||||
|
|
||||||
|
group := app.LDAPEntryToLDAPGroup(entry)
|
||||||
|
AssertLDAPGroupEquals(t, "LDAPEntryToLDAPGroup(entry) -> group", group, expectedGroup)
|
||||||
|
|
||||||
|
json := app.LDAPGroupToGin(group)
|
||||||
|
AssertLDAPGroupEquals(t, "LDAPGroupToGin(group) -> json", json, expectedGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleResponse(t *testing.T) {
|
||||||
|
for errorCode := range ldap.LDAPResultCodeMap {
|
||||||
|
expectedMessage := RandString(16)
|
||||||
|
LDAPerr := ldap.NewError(errorCode, errors.New(expectedMessage))
|
||||||
|
res := gin.H{
|
||||||
|
"error": LDAPerr,
|
||||||
|
}
|
||||||
|
LDAPResult := ldap.LDAPResultCodeMap[errorCode]
|
||||||
|
|
||||||
|
handledResponseError := (app.HandleResponse(res))["error"].(gin.H)
|
||||||
|
|
||||||
|
AssertEquals(t, `HandleResponse(res)["error"]["code"]`, handledResponseError["code"].(uint16), errorCode)
|
||||||
|
AssertEquals(t, `HandleResponse(res)["error"]["result"]`, handledResponseError["result"].(string), LDAPResult)
|
||||||
|
AssertEquals(t, `HandleResponse(res)["error"]["message"]`, handledResponseError["message"].(string), expectedMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := gin.H{
|
||||||
|
"ok": true,
|
||||||
|
"status": RandInt(0, 600),
|
||||||
|
}
|
||||||
|
|
||||||
|
handledResponse := app.HandleResponse(res)
|
||||||
|
|
||||||
|
AssertEquals(t, `HandleResponse(res)["ok"]`, handledResponse["ok"].(bool), res["ok"].(bool))
|
||||||
|
AssertEquals(t, `HandleResponse(res)["satus"]`, handledResponse["status"].(int), res["status"].(int))
|
||||||
|
AssertEquals(t, `HandleResponse(res)["error"]`, handledResponse["error"], nil)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user