|
@@ -0,0 +1,401 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "crypto/sha256"
|
|
|
+ "crypto/rand"
|
|
|
+ "math/big"
|
|
|
+ "database/sql"
|
|
|
+ "encoding/hex"
|
|
|
+ "log"
|
|
|
+ // "strconv"
|
|
|
+ "net/http"
|
|
|
+
|
|
|
+ "github.com/gin-gonic/gin"
|
|
|
+ _ "github.com/mattn/go-sqlite3"
|
|
|
+)
|
|
|
+
|
|
|
+type User struct {
|
|
|
+ ID int
|
|
|
+ Name string
|
|
|
+ UIDNumber int
|
|
|
+ PrimaryGroup int
|
|
|
+ OtherGroups string
|
|
|
+ GivenName string
|
|
|
+ SN string
|
|
|
+ Mail string
|
|
|
+ LoginShell string
|
|
|
+ HomeDirectory string
|
|
|
+ Disabled int
|
|
|
+ PassSHA256 string
|
|
|
+ OTPSecret string
|
|
|
+ YubiKey string
|
|
|
+ SSHKeys string
|
|
|
+ CustAttr string
|
|
|
+}
|
|
|
+
|
|
|
+// Structure représentant un groupe LDAP
|
|
|
+type LDAPGroup struct {
|
|
|
+ ID int `json:"id"`
|
|
|
+ Name string `json:"name"`
|
|
|
+ GIDNumber int `json:"gidnumber"`
|
|
|
+}
|
|
|
+
|
|
|
+func createTable(db *sql.DB) {
|
|
|
+ query := `
|
|
|
+CREATE TABLE IF NOT EXISTS users (
|
|
|
+ id INTEGER PRIMARY KEY,
|
|
|
+ name TEXT NOT NULL,
|
|
|
+ uidnumber INTEGER NOT NULL,
|
|
|
+ primarygroup INTEGER NOT NULL,
|
|
|
+ othergroups TEXT DEFAULT '',
|
|
|
+ givenname TEXT DEFAULT '',
|
|
|
+ sn TEXT DEFAULT '',
|
|
|
+ mail TEXT DEFAULT '',
|
|
|
+ loginshell TEXT DEFAULT '',
|
|
|
+ homedirectory TEXT DEFAULT '',
|
|
|
+ disabled SMALLINT DEFAULT 0,
|
|
|
+ passsha256 TEXT DEFAULT '',
|
|
|
+ otpsecret TEXT DEFAULT '',
|
|
|
+ yubikey TEXT DEFAULT '',
|
|
|
+ sshkeys TEXT DEFAULT '',
|
|
|
+ custattr TEXT DEFAULT '{}'
|
|
|
+);
|
|
|
+`
|
|
|
+ _, err := db.Exec(query)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatalf("Failed to create table: %v", err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func hashPassword(password string) string {
|
|
|
+ hash := sha256.New()
|
|
|
+ hash.Write([]byte(password))
|
|
|
+ return hex.EncodeToString(hash.Sum(nil))
|
|
|
+}
|
|
|
+
|
|
|
+func generateRandomPassword(maxLength int) string {
|
|
|
+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_+=<>?/|"
|
|
|
+ password := make([]byte, maxLength)
|
|
|
+ for i := range password {
|
|
|
+ charIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
|
|
+ password[i] = charset[charIndex.Int64()]
|
|
|
+ }
|
|
|
+ return string(password)
|
|
|
+}
|
|
|
+
|
|
|
+func generatePasswordSHA256(maxLength int) string {
|
|
|
+ randomPassword := generateRandomPassword(maxLength)
|
|
|
+ return hashPassword(randomPassword)
|
|
|
+}
|
|
|
+
|
|
|
+func authenticateUser(db *sql.DB, name, hashedPassword string) bool {
|
|
|
+ var user User
|
|
|
+ query := "SELECT * FROM users WHERE name = ? AND passsha256 = ?"
|
|
|
+ err := db.QueryRow(query, name, hashedPassword).Scan(
|
|
|
+ &user.ID, &user.Name, &user.UIDNumber, &user.PrimaryGroup, &user.OtherGroups,
|
|
|
+ &user.GivenName, &user.SN, &user.Mail, &user.LoginShell, &user.HomeDirectory,
|
|
|
+ &user.Disabled, &user.PassSHA256, &user.OTPSecret, &user.YubiKey,
|
|
|
+ &user.SSHKeys, &user.CustAttr,
|
|
|
+ )
|
|
|
+ return err == nil
|
|
|
+}
|
|
|
+
|
|
|
+// Fonction pour récupérer le dernier `uidnumber` dans la table `users`
|
|
|
+func getLastUIDNumber(db *sql.DB) (int, error) {
|
|
|
+ // Requête SQL pour récupérer le dernier uidnumber
|
|
|
+ var lastUIDNumber int
|
|
|
+ err := db.QueryRow("SELECT MAX(uidnumber) FROM users").Scan(&lastUIDNumber)
|
|
|
+ if err != nil {
|
|
|
+ if err == sql.ErrNoRows {
|
|
|
+ // Si aucune ligne n'est trouvée, retourner 0
|
|
|
+ return 0, nil
|
|
|
+ }
|
|
|
+ return 0, err
|
|
|
+ }
|
|
|
+ return lastUIDNumber, nil
|
|
|
+}
|
|
|
+
|
|
|
+func createUser(db *sql.DB, name, password string) bool {
|
|
|
+ hashedPassword := hashPassword(password)
|
|
|
+ query := "INSERT INTO users (name, passsha256) VALUES (?, ?)"
|
|
|
+ _, err := db.Exec(query, name, hashedPassword)
|
|
|
+ return err == nil
|
|
|
+}
|
|
|
+
|
|
|
+func fetchUsers(db *sql.DB) []User {
|
|
|
+ query := "SELECT id, name, sn, givenname, mail FROM users"
|
|
|
+ rows, err := db.Query(query)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatalf("Failed to fetch users: %v", err)
|
|
|
+ }
|
|
|
+ defer rows.Close()
|
|
|
+
|
|
|
+ var users []User
|
|
|
+ for rows.Next() {
|
|
|
+ var user User
|
|
|
+ err := rows.Scan(&user.ID, &user.Name, &user.SN, &user.GivenName, &user.Mail)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatalf("Failed to scan user: %v", err)
|
|
|
+ }
|
|
|
+ users = append(users, user)
|
|
|
+ }
|
|
|
+ return users
|
|
|
+}
|
|
|
+
|
|
|
+func fetchUserByID(db *sql.DB, id string) User {
|
|
|
+ query := "SELECT * FROM users WHERE id = ?"
|
|
|
+ var user User
|
|
|
+ err := db.QueryRow(query, id).Scan(
|
|
|
+ &user.ID, &user.Name, &user.UIDNumber, &user.PrimaryGroup, &user.OtherGroups,
|
|
|
+ &user.GivenName, &user.SN, &user.Mail, &user.LoginShell, &user.HomeDirectory,
|
|
|
+ &user.Disabled, &user.PassSHA256, &user.OTPSecret, &user.YubiKey,
|
|
|
+ &user.SSHKeys, &user.CustAttr,
|
|
|
+ )
|
|
|
+ if err != nil {
|
|
|
+ log.Fatalf("Failed to fetch user: %v", err)
|
|
|
+ }
|
|
|
+ return user
|
|
|
+}
|
|
|
+
|
|
|
+func updateUser(db *sql.DB, id, name, givenName, sn, mail string, sshkeys string) {
|
|
|
+ query := "UPDATE users SET name = ?, givenname = ?, sn = ?, mail = ?, sshkeys = ? WHERE id = ?"
|
|
|
+ _, err := db.Exec(query, name, givenName, sn, mail, sshkeys, id)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatalf("Failed to update user: %v", err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Fonction pour récupérer tous les groupes LDAP
|
|
|
+func fetchLDAPGroups(db *sql.DB) ([]LDAPGroup, error) {
|
|
|
+ // Préparer la requête SQL pour récupérer les groupes
|
|
|
+ rows, err := db.Query("SELECT id, name, gidnumber FROM ldapgroups")
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ defer rows.Close()
|
|
|
+
|
|
|
+ // Initialiser un tableau pour stocker les groupes
|
|
|
+ var groups []LDAPGroup
|
|
|
+
|
|
|
+ // Parcourir les résultats de la requête
|
|
|
+ for rows.Next() {
|
|
|
+ var group LDAPGroup
|
|
|
+ if err := rows.Scan(&group.ID, &group.Name, &group.GIDNumber); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ // Ajouter le groupe à la liste
|
|
|
+ groups = append(groups, group)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Vérifier si des erreurs sont survenues lors du parcours des lignes
|
|
|
+ if err := rows.Err(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return groups, nil
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ // Initialize database connection
|
|
|
+ db, err := sql.Open("sqlite3", "./users.db")
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal(err)
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ // Create table if it doesn't exist
|
|
|
+ createTable(db)
|
|
|
+
|
|
|
+ // Initialize Gin router
|
|
|
+ router := gin.Default()
|
|
|
+
|
|
|
+ // Serve HTML templates
|
|
|
+ router.LoadHTMLGlob("templates/*")
|
|
|
+
|
|
|
+ // Middleware for session authentication
|
|
|
+ router.Use(func(c *gin.Context) {
|
|
|
+ // Skip authentication check for login page
|
|
|
+ if c.Request.URL.Path == "/" || c.Request.URL.Path == "/login" {
|
|
|
+ c.Next()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // Check if user is authenticated
|
|
|
+ session, err := c.Cookie("session")
|
|
|
+ if err != nil || session != "authenticated" {
|
|
|
+ c.Redirect(http.StatusFound, "/")
|
|
|
+ c.Abort()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ c.Next()
|
|
|
+ })
|
|
|
+
|
|
|
+ // Routes
|
|
|
+ router.GET("/", func(c *gin.Context) {
|
|
|
+ c.HTML(http.StatusOK, "login.html", nil)
|
|
|
+ })
|
|
|
+
|
|
|
+ router.POST("/login", func(c *gin.Context) {
|
|
|
+ name := c.PostForm("name")
|
|
|
+ password := c.PostForm("password")
|
|
|
+
|
|
|
+ if authenticateUser(db, name, hashPassword(password)) {
|
|
|
+ // c.String(http.StatusOK, "Login successful!")
|
|
|
+ // Création d'un cookie de session
|
|
|
+ c.SetCookie("session", "authenticated", 3600, "/", "localhost", false, true)
|
|
|
+ c.Redirect(http.StatusFound, "/dashboard")
|
|
|
+ } else {
|
|
|
+ c.String(http.StatusUnauthorized, "Invalid credentials")
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ router.GET("/dashboard", func(c *gin.Context) {
|
|
|
+ users := fetchUsers(db)
|
|
|
+ c.HTML(http.StatusOK, "dashboard.html", gin.H{"users": users})
|
|
|
+ })
|
|
|
+
|
|
|
+ router.POST("/edit/:id", func(c *gin.Context) {
|
|
|
+ id := c.Param("id")
|
|
|
+ name := c.PostForm("name")
|
|
|
+ givenName := c.PostForm("givenname")
|
|
|
+ sn := c.PostForm("sn")
|
|
|
+ mail := c.PostForm("mail")
|
|
|
+ sshkeys := c.PostForm("sshkeys")
|
|
|
+ updateUser(db, id, name, givenName, sn, mail, sshkeys)
|
|
|
+ c.Redirect(http.StatusFound, "/dashboard")
|
|
|
+ })
|
|
|
+
|
|
|
+ router.GET("/edit/:id", func(c *gin.Context) {
|
|
|
+ id := c.Param("id")
|
|
|
+ user := fetchUserByID(db, id)
|
|
|
+ groups, err := fetchLDAPGroups(db)
|
|
|
+ if err != nil {
|
|
|
+ log.Println("Erreur lors de la récupération des groupes :", err)
|
|
|
+ c.String(http.StatusInternalServerError, "Erreur lors de la récupération des groupes")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ c.HTML(http.StatusOK, "edit.html", gin.H{
|
|
|
+ "user": user,
|
|
|
+ "groups": groups,
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ // Route GET pour afficher le formulaire de création
|
|
|
+ router.GET("/new", func(c *gin.Context) {
|
|
|
+ // Rendre la page "edit.html" avec un utilisateur vide
|
|
|
+ c.HTML(http.StatusOK, "edit.html", gin.H{"user": map[string]string{
|
|
|
+ "ID": "",
|
|
|
+ "Name": "",
|
|
|
+ "GivenName": "",
|
|
|
+ "SN": "",
|
|
|
+ "Mail": "",
|
|
|
+ "SSHKeys": "",
|
|
|
+ }})
|
|
|
+ })
|
|
|
+
|
|
|
+/*
|
|
|
+ // Route POST pour insérer un nouvel utilisateur
|
|
|
+ router.POST("/new", func(c *gin.Context) {
|
|
|
+ name := c.PostForm("name")
|
|
|
+ givenName := c.PostForm("givenname")
|
|
|
+ sn := c.PostForm("sn")
|
|
|
+ mail := c.PostForm("mail")
|
|
|
+ sshkeys := c.PostForm("sshkeys")
|
|
|
+
|
|
|
+ // Appeler la fonction pour récupérer le dernier uidnumber
|
|
|
+ lastUID, err := getLastUIDNumber(db)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal(err)
|
|
|
+ }
|
|
|
+ nextUID := lastUID+1
|
|
|
+
|
|
|
+ // Génération automatique d'un mot de passe aléatoire
|
|
|
+ randomPassword := generateRandomPassword(32)
|
|
|
+ hashedPassword := hashPassword(randomPassword)
|
|
|
+
|
|
|
+ // Insérer dans la base de données
|
|
|
+ _, err := db.Exec(`
|
|
|
+ INSERT INTO users (name, uidnumber, givenname, sn, mail, passsha256, sshkeys)
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, name, nextUID, givenName, sn, mail, hashedPassword, sshkeys)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ log.Println("Erreur lors de l'insertion :", err)
|
|
|
+ c.String(http.StatusInternalServerError, "Erreur lors de l'ajout de l'utilisateur")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Redirection vers le tableau de bord après la création
|
|
|
+ c.Redirect(http.StatusFound, "/dashboard")
|
|
|
+ })
|
|
|
+
|
|
|
+
|
|
|
+ router.POST("/update/:id", func(c *gin.Context) {
|
|
|
+ userID := c.Param("id")
|
|
|
+
|
|
|
+ // Récupérer les valeurs du formulaire
|
|
|
+ name := c.PostForm("name")
|
|
|
+ sn := c.PostForm("sn")
|
|
|
+ givenname := c.PostForm("givenname")
|
|
|
+ mail := c.PostForm("mail")
|
|
|
+ primarygroup := c.PostForm("group")
|
|
|
+
|
|
|
+ // Convertir groupID en entier
|
|
|
+ groupIDInt, err := strconv.Atoi(groupID)
|
|
|
+ if err != nil {
|
|
|
+ log.Println("Erreur lors de la conversion du groupID:", err)
|
|
|
+ c.String(http.StatusBadRequest, "Erreur dans la conversion du groupID")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mettre à jour l'utilisateur dans la base de données
|
|
|
+ _, err = db.Exec(`
|
|
|
+ UPDATE users SET name = ?, sn = ?, givenname = ?, mail = ?, primarygroup = ?
|
|
|
+ WHERE id = ?`, name, sn, givenname, mail, groupIDInt, userID)
|
|
|
+ if err != nil {
|
|
|
+ log.Println("Erreur lors de la mise à jour de l'utilisateur :", err)
|
|
|
+ c.String(http.StatusInternalServerError, "Erreur lors de la mise à jour de l'utilisateur")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Rediriger vers la page dashboard après mise à jour
|
|
|
+ c.Redirect(http.StatusFound, "/dashboard")
|
|
|
+ })
|
|
|
+ */
|
|
|
+ /*
|
|
|
+ // Route pour afficher la liste des utilisateurs
|
|
|
+ router.GET("/users", func(c *gin.Context) {
|
|
|
+ users := fetchUsers(db) // Assurez-vous de disposer de la fonction fetchUsers qui retourne les utilisateurs
|
|
|
+ if err != nil {
|
|
|
+ log.Println("Erreur lors de la récupération des utilisateurs :", err)
|
|
|
+ c.String(http.StatusInternalServerError, "Erreur lors de la récupération des utilisateurs")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ c.HTML(http.StatusOK, "users.html", gin.H{
|
|
|
+ "users": users,
|
|
|
+ })
|
|
|
+ })
|
|
|
+ */
|
|
|
+
|
|
|
+ // Route pour afficher la liste des groupes
|
|
|
+ router.GET("/groups", func(c *gin.Context) {
|
|
|
+ groups, err := fetchLDAPGroups(db) // Utilisez la fonction fetchLDAPGroups que vous avez déjà définie
|
|
|
+ if err != nil {
|
|
|
+ log.Println("Erreur lors de la récupération des groupes :", err)
|
|
|
+ c.String(http.StatusInternalServerError, "Erreur lors de la récupération des groupes")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ c.HTML(http.StatusOK, "groups.html", gin.H{
|
|
|
+ "groups": groups,
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ // logout
|
|
|
+ router.GET("/logout", func(c *gin.Context) {
|
|
|
+ c.SetCookie("session", "", -1, "/", "localhost", false, true)
|
|
|
+ c.Redirect(http.StatusFound, "/")
|
|
|
+ })
|
|
|
+
|
|
|
+ // Start the server
|
|
|
+ router.Run(":8080")
|
|
|
+}
|
|
|
+
|
|
|
+
|