package main
import (
"github.com/go-ldap/ldap/v3"
"fmt"
log "github.com/sirupsen/logrus"
"math/rand"
"strings"
"errors"
"os"
)
const bindusername = "cn=admin,dc=deuxfleurs,dc=fr"
const adresse = "127.0.0.1"
const port = 1389
var bindpassword string
var all_names = make(map[string]struct{})
func printError(LDAPError error) {
if LDAPError != nil {
log.Fatal(LDAPError)
}
}
func createOU(l *ldap.Conn) error {
req := ldap.NewAddRequest("ou=groups,dc=deuxfleurs,dc=fr",nil)
req.Attribute("description",[]string{"OrganizationalUnit qui regroupe tous les groupes"})
req.Attribute("objectclass",[]string{"organizationalUnit", "top"})
req.Attribute("ou",[]string{"groups"})
req.Attribute("structuralobjectclass", []string{"organizationalUnit"})
err := l.Add(req)
if err != nil {
return err
}
req = ldap.NewAddRequest("ou=users,dc=deuxfleurs,dc=fr",nil)
req.Attribute("description",[]string{"OrganizationalUnit qui regroupe tous les utilisateurs"})
req.Attribute("objectclass",[]string{"organizationalUnit", "top"})
req.Attribute("ou",[]string{"users"})
req.Attribute("structuralobjectclass", []string{"organizationalUnit"})
err = l.Add(req)
return err
}
func generateName(r *rand.Rand) (name string) {
for only_one := true; only_one; _, only_one = all_names[name]{
name = fmt.Sprintf("%d",r.Int())
}
all_names[name] = struct{}{}
log.Debug(fmt.Sprintf("Name generated: %s.\n", name))
return
}
func createGroup(r *rand.Rand, l *ldap.Conn) (tab_AddRequest []ldap.AddRequest, err error) {
StructuralObjectClass := []string{"groupOfNames"}
ObjectClass := []string{"groupOfNames","top"}
for i := 0; i<20; i++ {
//Generate name and check if he is unique
name := generateName(r)
req := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=groups,dc=deuxfleurs,dc=fr",name),nil)
req.Attribute("description",[]string{generateName(r)})
req.Attribute("objectclass",ObjectClass)
req.Attribute("structuralobjectclass",StructuralObjectClass)
err = l.Add(req)
if err != nil {
log.Warn(fmt.Sprintf("Erreur survenue sur la création du [%d] groupe.\n",i))
return nil, err
}
tab_AddRequest = append(tab_AddRequest, *req)
}
return
}
func createUser(r *rand.Rand, l *ldap.Conn) (tab_AddRequest []ldap.AddRequest, err error) {
StructuralObjectClass := []string{"inetOrgPerson"}
ObjectClass := []string{"inetOrgPerson","organizationalPerson","person","top"}
for i := 0; i<20; i++ {
name := generateName(r)
req := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=users,dc=deuxfleurs,dc=fr",name),nil)
req.Attribute("displayname",[]string{generateName(r)})
req.Attribute("objectclass",ObjectClass)
req.Attribute("structuralobjectclass",StructuralObjectClass)
err = l.Add(req)
if err != nil {
log.Warn(fmt.Sprintf("Erreur survenue sur la création du [%d] user.\n",i))
return nil, err
}
tab_AddRequest = append(tab_AddRequest, *req)
}
return
}
func search_attributes(tab_Attributes []ldap.Attribute, tipe string) (*ldap.Attribute) {
for _,att := range tab_Attributes {
if att.Type == tipe {
return &att
}
}
return nil
// return ldap.Attribute{}
}
func test_attributes(l *ldap.Conn, tab_AddRequest []ldap.AddRequest, filter_objectclass, user_or_group string) (err error) {
for _, addRequest := range tab_AddRequest {
//On prend le cn en supposant qu'il est unique
cn := strings.Split(addRequest.DN,",")[0]
//On crée la requête pour la recherche
search_req := ldap.NewSearchRequest(
fmt.Sprintf("ou=%s,dc=deuxfleurs,dc=fr",user_or_group),
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectclass=%s)(%s))", filter_objectclass,cn),
[]string{"displayname","objectclass","structuralobjectclass"},
nil,
)
//On lance la recherche
result, err := l.Search(search_req)
if err != nil {
return err
}
if len(result.Entries) != 1 {
return errors.New("Test a trouvé plusieurs displaynames en commun ou en a trouvé aucun")
}
//On compare les attributs qu'on a reçu avec les attributs qu'on a envoyé
result_attributes := result.Entries[0].Attributes
log.Debug(fmt.Sprintf("La longueur est de %d, contient : \n %s.\n",len(result_attributes), result_attributes))
//Notre recherche crée un attribut par valeur, même si les valeurs viennent du même nom d'attribut
//Par exemple: objectclass possède 4 valeurs. Alors on aura 4 EntryAttribute qui contient chacune une des 4 valeurs de l'attribut objectclass
//j est l'indice qui représente la j-ème valeur de notre attribut
var j int
var att *ldap.Attribute
for i,attributes := range result_attributes {
//On cherche l'attribut de l'user i qui a le même nom que celui qu'on a reçu et qu'on traite dans cette boucle
if j == 0 {
att = search_attributes(addRequest.Attributes, attributes.Name)
if att == nil {
return errors.New(fmt.Sprintf("Error: test_attributes - Don't find match name attributes. We search %s.\n", attributes.Name))
}
}
log.Debug(fmt.Sprintf("Le nom de l'attribut est %s, sa valeur est: \n %s.", att.Type, att))
if j >= len(att.Vals) || att.Vals[j] != attributes.Values[0] {
return errors.New(fmt.Sprintf("Error: test_attributes - Theses values aren't the same: %d, %d",att.Vals, attributes.Values))
}
if i+1 < len(result_attributes) && result_attributes[i+1].Name == attributes.Name {
j += 1
} else { j = 0}
}
}
return nil
}
func clean(l *ldap.Conn, AddReq_users, AddReq_groups []ldap.AddRequest,user, group bool) (err error){
log.Debug("Debut clean")
if(user) {
for _,req := range AddReq_users {
delReq := ldap.NewDelRequest(req.DN,nil)
err = l.Del(delReq)
if err != nil {
return
}
}
}
if group {
for _,req := range AddReq_groups {
delReq := ldap.NewDelRequest(req.DN, nil)
err = l.Del(delReq)
if err != nil {
return
}
}
}
defer log.Debug("Fin clean")
return
}
func test_modify_attributes(l *ldap.Conn, r *rand.Rand, tab_AddReq []ldap.AddRequest, tab_type_name []string) (err error) {
for _, AddReq := range tab_AddReq {
modReq := ldap.NewModifyRequest(AddReq.DN,nil)
for _, type_name := range tab_type_name {
newName := generateName(r)
modReq.Replace(type_name, []string{newName})
att := search_attributes(AddReq.Attributes, type_name)
att.Vals[0] = newName
}
err = l.Modify(modReq)
if err != nil {
return
}
}
return
}
func add_user_in_groups(l *ldap.Conn, r *rand.Rand, users, groups []ldap.AddRequest) (err error) {
for _,group := range groups {
numberUsers := r.Intn(19) + 1 //Always a minimum of 1 user
list_users := []string{}
for i:=0; i < numberUsers; i++ {
list_users = append(list_users, users[i].DN)
}
modifyReq := ldap.NewModifyRequest( group.DN, nil)
modifyReq.Add("member", list_users)
err = l.Modify(modifyReq)
if err != nil {
log.Warn(fmt.Sprintf("Error: ModifyReq failed, func:add_users_in_groups from group:\n %d",group))
return
}
}
return
}
func delete_groups(l *ldap.Conn, groups []ldap.AddRequest) (list map[string][]string ,err error) {
list = make(map[string][]string)
for _, group := range groups {
//Get lists_users
cn := strings.Split(group.DN,",")[0]
search_req := ldap.NewSearchRequest(
"ou=groups,dc=deuxfleurs,dc=fr",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectclass=groupOfNames)(%s))",cn),
[]string{"member"},
nil,
)
res , err := l.Search(search_req)
if err != nil {
log.Warn(fmt.Sprintf("Error Search: func: delete_groups_and_check_memberOf, from group: \n %d", group))
return list, err
}
if len(res.Entries) != 1 {
err = errors.New(fmt.Sprintf("SearchResult get: %s, SearchResult wanted: 1", len(res.Entries)))
return list, err
}
EntryAtt := res.Entries[0].Attributes
list_users := []string{}
for _, att := range EntryAtt {
list_users = append(list_users ,att.Values[0])
}
//Del group
del := ldap.NewDelRequest( group.DN, nil)
err = l.Del(del)
if err != nil {
return list, err
}
list[group.DN] = list_users
}
return
}
func check_memberOf(l *ldap.Conn, list map[string][]string) (err error) {
//Check the memberOf of all users
for groupeDN,_ := range list{
search_req := ldap.NewSearchRequest(
"ou=users,dc=deuxfleurs,dc=fr",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,0 ,0, false,
fmt.Sprintf("(&(objectclass=inetOrgPerson)(memberOf=%s))",groupeDN),
[]string{"cn"},
nil,
)
res, err := l.Search(search_req)
if err != nil {
return err
}
if len(res.Entries) != 0 {
err = errors.New(fmt.Sprintf("L'user '%s' a encore le DN d'un groupe supprimé: %s",res.Entries[0].Attributes[0].Values[0],groupeDN))
return err
}
}
return err
}
func reconnect(l *ldap.Conn) (l_nouv *ldap.Conn, err error){
l.Close()
l_nouv, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d",adresse,port))
if err != nil {
return
}
err = l_nouv.Bind(bindusername, bindpassword)
return
}
func main() {
var ok bool
bindpassword, ok = os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW")
if !ok {
if len(os.Args) == 2 {
bindpassword = os.Args[1]
} else {
bindpassword = ""
}
}
log.Info(fmt.Sprintf("Password selected: %s",bindpassword))
//log.SetLevel(log.TraceLevel)
//Create a connection with Bottin server
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port))
//l.Debug = true
printError(err)
//Bind with the admin account generated
err = l.Bind(bindusername, bindpassword)
printError(err)
//Create our object Rand, it's important to always have the same values
source := rand.NewSource(666475745)
r := rand.New(source)
log.Info(fmt.Sprintf("The seed of the rand object is %d.\n",r.Seed))
//Create user and groups OrgaUnit
err = createOU(l)
if ldap.IsErrorWithCode(err, uint16(68)) {
log.Warn("Les OrganizationalUnit users et groups sont déjà présents.")
}else {
printError(err)
log.Info("Création des OU de groups et users")
}
//Create random groups
tab_AddRequest_groups, err := createGroup(r, l)
printError(err)
log.Info(fmt.Sprintf("Création des groupes aléatoirement réussi: %d\n", len(tab_AddRequest_groups)))
//Create random users
tab_AddRequest_users, err := createUser(r, l)
printError(err)
log.Info(fmt.Sprintf("Création des users aléatoirement réussi: %d\n", len(tab_AddRequest_users)))
//Search and compare attribute Users. (We keep Attribute object from 'Create random users' and compare with the result of our search)
err = test_attributes(l,tab_AddRequest_users, "inetOrgPerson","users")
printError(err)
log.Info("Tous les attributs users insérés dans Consul ont été vérifiés..\n")
//Search and compare attributes Groups
err = test_attributes(l,tab_AddRequest_groups, "groupOfNames","groups")
printError(err)
log.Info("Tous les attributs groups insérés dans Consul ont été vérifiés.\n")
//Close the connection and open an other. If we don't do this, bottin server send a wrong answer. Comment this part if you want to try this
l,err = reconnect(l)
printError(err)
//Modify attributes users and groups.
//Modify users' attributes and check them
log.Debug(fmt.Sprintf("Les valeurs sont:\n %s", tab_AddRequest_users))
err = test_modify_attributes(l, r, tab_AddRequest_users, []string{"displayname"})
printError(err)
log.Debug("Modifications users faites")
//Check if the attributes are correct:
err = test_attributes(l,tab_AddRequest_users, "inetOrgPerson", "users")
printError(err)
log.Info("Les modifications ont bien été prises en compte")
log.Debug(fmt.Sprintf("Les nouvelles valeurs sont:\n %s", tab_AddRequest_users))
//Modify users' attributes and check them
err = test_modify_attributes(l, r, tab_AddRequest_groups, []string{"description"})
printError(err)
log.Info("Modifications groups faites")
//Check if the attributes are correct:
err = test_attributes(l,tab_AddRequest_groups, "groupOfNames", "groups")
printError(err)
log.Info("Les modifications ont bien été prises en compte")
//Close the connection
l, err = reconnect(l)
printError(err)
//Add users in group, search them, delete several samples and search again to be sur it's good
err = add_user_in_groups(l, r, tab_AddRequest_users, tab_AddRequest_groups)
printError(err)
log.Info("Ajout d'users dans les groupes fait")
//Close the connection
l, err = reconnect(l)
printError(err)
list, err := delete_groups(l, tab_AddRequest_groups)
printError(err)
log.Info("groupe supprimé")
l,err = reconnect(l)
printError(err)
err = check_memberOf(l, list)
printError(err)
log.Info("Le memberOf a été correctement vidé")
//Clean: Delete all users and groups (not OU users and groups)
err = clean(l, tab_AddRequest_users, tab_AddRequest_groups, true, false)
printError(err)
log.Info("Clean succes")
return
}