new add role feature
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2024-01-04 09:43:59 +11:00
parent f7168d465a
commit 0619b497f7
5 changed files with 105 additions and 9 deletions

View File

@@ -29,11 +29,12 @@ Written by Nathan Coad (nathan.coad@dell.com)
| LOG_FILE | Specify the name/path of file to write log messages to | /var/log/smt.log | ./smt.log
| BIND_IP | Specify the local IP address to bind to. | 127.0.0.1 | Primary IPv4 address |
| BIND_PORT | Specify the TCP/IP port to bind to. | 443 | 8443 |
| LDAP_BIND_ADDRESS | If LDAP integration is needed, specify the LDAP Bind address | ldaps://dc.domain.com:636 | No default specified |
| TLS_KEY_FILE | Specify the filename of the TLS certificate private key (must be unencrypted) in PEM format | key.pem | privkey.pem |
| TLS_CERT_FILE | Specify the filename of the TLS certificate file in PEM format | cert.pem | cert.pem |
| TOKEN_HOUR_LIFESPAN | Number of hours that the JWT token returned at login is valid | 12 | No default specified, must define this value |
| API_SECRET | Secret to use when generating JWT token | 3c55990bd479322e2053db3a8 | No default specified, must define this value |
| INITIAL_PASSWORD | Password to set for builtin Administrator account created when first started, can remove this value after first start. Can specify in plaintext or bcrypt hash | $2a$10$s39a82wrRAdOJVZEkkrSReVnXprz5mxU30ZBO.dHPYTncQCsUD9ce | password
| INITIAL_PASSWORD | Password to set for builtin Administrator account created when first started, can remove this value after first start. Can specify in plaintext or bcrypt hash | $2a$10$s39a82wrRAdOJVZEkkrSReVnXprz5mxU30ZBO.dHPYTncQCsUD9ce | password |
| SECRETS_KEY | Key to use for AES256 GCM encryption. Must be exactly 32 bytes | AES256Key-32Characters1234567890 | No default specified, must define this value or use /api/unlock at runtime |
If the TLS certificate and key files cannot be located in the specified location, a self signed certificate will be generated with a 1 year validity period.

View File

@@ -2,6 +2,7 @@ package controllers
import (
"errors"
"fmt"
"html"
"log"
"net/http"
@@ -29,6 +30,13 @@ type DeleteInput struct {
UserName string `json:"userName" binding:"required"`
}
type AddRoleInput struct {
RoleName string `json:"roleName" binding:"required"`
LdapGroup string `json:"ldapGroup"`
ReadOnly bool `json:"readOnly"`
Admin bool `json:"admin"`
}
func DeleteUser(c *gin.Context) {
var input DeleteInput
@@ -63,7 +71,7 @@ func DeleteUser(c *gin.Context) {
}
}
func Register(c *gin.Context) {
func RegisterUser(c *gin.Context) {
var input RegisterInput
if err := c.ShouldBindJSON(&input); err != nil {
@@ -117,7 +125,45 @@ func Register(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "registration success"})
c.JSON(http.StatusOK, gin.H{"message": "user registration success"})
}
func AddRole(c *gin.Context) {
var input AddRoleInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// define the new role properties
r := models.Role{}
r.RoleName = input.RoleName
r.ReadOnly = input.ReadOnly
r.Admin = input.Admin
r.LdapGroup = input.LdapGroup
// Check if role already exists
testRole, _ := models.GetRoleByName(r.RoleName)
log.Printf("AddRole checking if role '%s' already exists\n", r.RoleName)
if (models.Role{} == testRole) {
log.Printf("AddRole confirmed no existing rolename\n")
} else {
errorString := fmt.Sprintf("attempt to register conflicting rolename '%s'", r.RoleName)
log.Printf("Register error : '%s'\n", errorString)
c.JSON(http.StatusBadRequest, gin.H{"error": errorString})
return
}
_, err := r.AddRole()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"Error creating role": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "role creation success"})
}
func Login(c *gin.Context) {

View File

@@ -248,8 +248,10 @@ func main() {
adminOnly := router.Group("/api/admin")
adminOnly.Use(middlewares.JwtAuthAdminMiddleware())
adminOnly.POST("/user/delete", controllers.DeleteUser)
adminOnly.POST("/user/register", controllers.Register)
adminOnly.POST("/user/register", controllers.RegisterUser) // TODO deprecate
adminOnly.POST("/user/add", controllers.RegisterUser)
adminOnly.GET("/roles", controllers.GetRoles)
adminOnly.POST("/role/add", controllers.AddRole)
adminOnly.GET("/users", controllers.GetUsers)
// TODO Make unlock an admin only function

View File

@@ -1,10 +1,14 @@
package models
import "log"
import (
"errors"
"log"
)
type Role struct {
RoleId int `db:"RoleId"`
RoleName string `db:"RoleName"`
LdapGroup string `db:"LdapGroup"`
ReadOnly bool `db:"ReadOnly"`
Admin bool `db:"Admin"`
}
@@ -35,3 +39,45 @@ func QueryRoles() ([]Role, error) {
return results, nil
}
// AddRole adds a new role definition to the database
func (r *Role) AddRole() (*Role, error) {
var err error
// Validate role not already in use
_, err = GetRoleByName(r.RoleName)
// TODO
if err != nil && err.Error() == "role not found" {
log.Printf("AddRole confirmed no existing role, continuing with creation of role '%s'\n", r.RoleName)
result, err := db.NamedExec(("INSERT INTO roles (RoleName, ReadOnly, Admin, LdapGroup) VALUES (:RoleName, :ReadOnly, :Admin, :LdapGroup);"), r)
if err != nil {
log.Printf("AddRole error executing sql record : '%s'\n", err)
return &Role{}, err
} else {
affected, _ := result.RowsAffected()
id, _ := result.LastInsertId()
log.Printf("AddRole insert returned result id '%d' affecting %d row(s).\n", id, affected)
}
} else {
log.Printf("AddRole RoleName already exists : '%v'\n", err)
}
return r, nil
}
func GetRoleByName(rolename string) (Role, error) {
var r Role
// Query database for matching user object
err := db.QueryRowx("SELECT * FROM roles WHERE RoleName=?", rolename).StructScan(&r)
if err != nil {
return r, errors.New("role not found")
}
return r, nil
}

View File

@@ -4,5 +4,6 @@ INITIAL_PASSWORD=Password123
TOKEN_HOUR_LIFESPAN=168
BIND_IP=
BIND_PORT=8443
LDAP_BIND_ADDRESS=
TLS_KEY_FILE=key.pem
TLS_CERT_FILE=cert.pem