63 Commits
0.3.3 ... main

Author SHA1 Message Date
2754fb8144 revert css to customised but older
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-04 15:02:21 +10:00
4650a971a3 css update
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-04 14:54:05 +10:00
8cd1292a41 update packages
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-04 14:46:39 +10:00
ea5198a5b9 bugfix
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-11 21:01:06 +10:00
d000469836 golang updates
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-18 11:08:55 +10:00
02061f5b26 reduce logging
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-19 13:52:08 +10:00
ea3e8ddfbc re-add cache rebuild
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-05 09:47:54 +10:00
8182b899cf upgrade dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-05 09:45:12 +10:00
ae5b864feb update for new dell site
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-15 08:24:05 +10:00
4a6a7270f9 improve input checking when retrieving secret
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-08 08:36:20 +10:00
a7beb94341 ftps doesnt seem t owork
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-08 14:06:37 +10:00
4a0e98bab8 test secure ftp
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-08 13:59:00 +10:00
66a1917e6f return updated permission details in UpdatePermissionHandler
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-04 08:56:14 +11:00
526161f6b4 bugfix permission delete when only permissionId specified
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-03 11:10:36 +11:00
ff16acc816 add support to update permissions
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-03 10:42:11 +11:00
ee822b5c9d if username in UPN format for login try searching both user and full UPN string
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 16:55:11 +11:00
e427184310 test logic fix for ldap users not in an ldap group
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 15:11:24 +11:00
5719ce8f5d creating ldap user was not setting ldapuser flag
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 14:41:15 +11:00
a78f2b7c88 improve adding ldap user
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 14:13:21 +11:00
1171a7bbaa update readme
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-13 11:26:55 +11:00
8ff92e206e include count in listsecrets
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:33:08 +11:00
19ffc9e683 log schema
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:23:23 +11:00
968bcf1b7a fix sql statement
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:22:14 +11:00
77d063867a set schema version better
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:20:52 +11:00
c82bffe421 try using schema version better
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:17:38 +11:00
b801563074 cleanup
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:11:08 +11:00
4bc430633e fix audit message
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 12:09:02 +11:00
69a25fbb09 add username to secret retrieve audit log
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 11:48:11 +11:00
840b9f4863 include go version
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 10:06:14 +11:00
5b87ef0d30 whoops
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 10:04:08 +11:00
bfc734a6d1 update golang version
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-23 10:03:33 +11:00
b5c9b5ce19 more audit logs
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-22 19:05:26 +11:00
9f0dafd4fd remove artificial restriction on multiple secret retrieval
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-22 16:27:06 +11:00
abaa291a14 add search by username
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-22 16:13:35 +11:00
de1a076d64 update docs
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 11:22:57 +11:00
1b5a2e89dd db fix
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 11:01:38 +11:00
8799f0f796 add client IP to audit logs
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 10:56:37 +11:00
317e0ab83d bugfix
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 10:40:27 +11:00
116a9e827b fix extra comma
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 09:21:44 +11:00
2ab6240a24 add lastlogin for user
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 09:17:31 +11:00
bb3bf3093d add missing secretId to audit logs
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-17 14:40:36 +11:00
1d1aa098a9 change eventlog message
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-17 14:38:24 +11:00
f68bd9637d add event log retrieval
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-17 12:20:01 +11:00
5f63ee235b remove some logging containing hashes
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:26:04 +11:00
092fe32baf missed one
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:21:38 +11:00
44d3bc71ed remove extra debug statements
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:20:46 +11:00
dc3c5d1068 test new PrintStructContents
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:18:46 +11:00
d834a5c362 blah
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:16:37 +11:00
9e0c1e7cd7 blah
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:12:36 +11:00
9ac729b684 more testing
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:10:26 +11:00
7f43662cbc try debugging the debugging
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:05:48 +11:00
b35d365467 test
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:02:17 +11:00
77d487c1ce blah
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 17:00:53 +11:00
f5827ef432 debug
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:58:37 +11:00
6e1a28d2df try this
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:55:28 +11:00
5c3b2e19cf another fix
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:49:37 +11:00
e109cd084d try again to fix lastupdated
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:40:51 +11:00
f29c733080 fix LastUpdated default value
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:35:28 +11:00
97cd75b0d7 fix lastupdated definition
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:22:45 +11:00
b278a3c7d8 add last updated tracking for secrets
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:21:41 +11:00
498dd9a8c3 allow use of secretId when performing operations on secrets
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 16:08:40 +11:00
c99ffa8368 resume dell deploy
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 15:19:30 +11:00
8fec84c118 another fix
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-16 15:18:42 +11:00
20 changed files with 1099 additions and 510 deletions

View File

@@ -81,31 +81,29 @@ steps:
- sudo bash -c 'mv /home/l075239/smt/test.env /home/l075239/smt/.env'
- sudo bash -c '/etc/init.d/smt restart'
- name: dell-deploy
# # https://github.com/cschlosser/drone-ftps/blob/master/README.md
image: cschlosser/drone-ftps
when:
event:
- tag
environment:
FTP_USERNAME:
from_secret: FTP_USERNAME
FTP_PASSWORD:
from_secret: FTP_PASSWORD
PLUGIN_HOSTNAME: ftp.emc.com:21
PLUGIN_SECURE: false
PLUGIN_VERIFY: false
PLUGIN_CHMOD: false
#PLUGIN_DEBUG: false
PLUGIN_INCLUDE: ^smt$,^smt_checksum.txt$
PLUGIN_EXCLUDE: ^\.git/$,^\controllers/$,^\middlewares/$,^\models/$,^\utils/$,^\pkg.build/$,^\pkg.mod/$,^\www/$
- name: dell-sftp-deploy
image: hypervtechnics/drone-sftp
settings:
host: deft.dell.com
username:
from_secret: DELLFTP_USER
password:
from_secret: DELLFTP_PASS
port: 22
source: ./
filter: smt*
clean: false
target: /
overwrite: true
verbose: true
- name: rebuild-cache-with-filesystem
image: meltwater/drone-cache
pull: true
when:
event:
- tag
#when:
# event:
# - tag
settings:
backend: "filesystem"
#debug: true

117
README.md
View File

@@ -6,6 +6,8 @@ Build Date: `{BUILDTIME}`
Build Hash: `{SHA1VER}`
Go version: `{RUNTIME}`
Written by Nathan Coad (nathan.coad@dell.com)
## Overview
@@ -80,6 +82,10 @@ WantedBy=multi-user.target
```
## API Usage
API calls return http status code of **200** if successful, or **4xx** if unsuccessful. API calls that are unsuccessful will also include a JSON response with the key `error` and a value of the reason for the failure. Successful API calls will include a `message` key with a value of either success or something more detailed such as "user deletion success"
API calls that create or modify a record will include the created/updated record in the JSON response.
### Login
**POST** `/api/login`
@@ -92,7 +98,9 @@ Body
```
This API call will return a JWT token that must be present for any other API calls to succeed. The validity duration of this token is based on the configured TOKEN_HOUR_LIFESPAN value. JWT token is returned as value of `access_token`, and must be supplied via a HTTP header in the form `"Authorization: Bearer <JWT_TOKEN>"` for all subsequent API calls.
### Unlock
#### Admin Only operations
#### Unlock
**POST** `/api/admin/unlock`
Body
@@ -106,6 +114,11 @@ If the SECRETS_KEY environment variable is not defined, this API call to unlock
This API call can only be made once after the service has started. Subsequent calls will receive an error until the service is restarted.
#### Event Logs
**GET** `/api/admin/logs`
This operation can only be performed by a user with that is admin enabled. Lists all event logs.
### User Operations
#### Register User
@@ -133,6 +146,17 @@ Body
}
```
Add an ldap user
Body
```
{
"userName": "Ldap User",
"groupName": "Users",
"ldapUser": true
}
```
Registering a user requires specifying the group to which the user will belong. There are 2 built-in groups, with groupName of 'Administrators' or 'Users' and corresponding groupId of 1 and 2 respectively. Available groups can be retrieved via the `/api/admin/groups/list`
This operation can only be performed by a user that is a member of a group with the admin flag enabled, or a user who has the admin flag enabled individually on their database record.
@@ -178,6 +202,17 @@ Body
}
```
Create a new permission for a group
Body
```
{
"Description": "Group access to default safe",
"safeId": 1,
"groupId": 1
}
```
Creates a new permission mapping user/group to safe. Currently the create permission operation requires knowing the correct user Id or group Id, as well as the safe Id onto which permissions will be granted. This operation can only be performed by a user that is admin enabled.
#### Delete Permission
@@ -189,7 +224,7 @@ Delete permission by specifying description
Body
```
{
"Description":"Readonly access to default safe"
"Description": "Readonly access to default safe"
}
```
@@ -199,7 +234,7 @@ Delete permission by specifying permission id
Body
```
{
"permissionId":2
"permissionId": 2
}
```
@@ -208,6 +243,22 @@ Deletes a permission mapping either a user or a group to a safe. Either the perm
Deleting a permission should be performed prior to deleting any groups specified in that permission.
#### Update Permission
**POST** `/api/admin/permission/update`
Change description of permission
Body
```
{
"permissionId": 2
"Description": "New Permission Description"
}
```
Updates an existing permission. The permissionId must be specified. This operation can only be performed by a user that is admin enabled.
### Group Operations
#### List Groups
@@ -318,7 +369,7 @@ This operation can only be performed by a user that is admin enabled, or that is
### Secrets Operations
#### Store
#### Store Secret
**POST** `/api/secret/add`
Store secret if user only has access to a single safe
@@ -351,39 +402,55 @@ If a secret exists with a matching deviceName and deviceCategory in a safe that
If the current user has access to multiple safes, then the destination safeId will also need to be specified.
#### Retrieve
#### Get Secret
**POST** `/api/secret/get`
Body
```
{
"deviceName": "",
"deviceName": "device.example.com",
"deviceCategory": "",
"userName": ""
"userName": "example-user"
}
```
Must be logged in to execute this command. Only secrets registered with the current user's RoleId can be retrieved.
Body
```
{
"secretId": 29
}
```
Must be logged in to execute this command. Only secrets that the logged in user has access to can be retrieved.
Either deviceName or deviceCategory can be specified (or both). Wildcards are supported for both deviceName and deviceCategory fields. userName can also be specified in conjunction with deviceName or deviceCategory.
1. The percent sign % wildcard matches any sequence of zero or more characters.
2. The underscore _ wildcard matches any single character.
If the secretId is known, that can also be used to query for the secret. In this case the secretId uniquely identifies the secret so no other parameters are necessary.
#### Search by device name
**GET** `/api/secret/retrieve/name/<searchname>`
Search for a secret specified by deviceName using a GET request.
Must be logged in to execute this command. Only secrets registered with the current user's RoleId can be retrieved.
Must be logged in to execute this command. Only secrets in safes that the current user can access can be retrieved.
#### Search by device category
**GET** `/api/secret/retrieve/category/<searchname>`
Search for a secret specified by deviceCategory using a GET request.
Must be logged in to execute this command. Only secrets registered with the current user's RoleId can be retrieved.
Must be logged in to execute this command. Only secrets in safes that the current user can access can be retrieved.
#### Update
#### Search by username
**GET** `/api/secret/retrieve/user/<searchname>`
Search for a secret specified by userName using a GET request.
Must be logged in to execute this command. Only secrets in safes that the current user can access can be retrieved.
#### Update Secret
**POST** `/api/secret/update`
Update secret value for existing secret record
@@ -415,10 +482,36 @@ The values specified in deviceName and deviceCategory must match exactly one exi
If a user has read-write access to multiple safes, then specifying a different safeId to the one currently holding the secret will allow the secret to be moved into the other safe.
#### List
#### List Secrets
**GET** `/api/secret/list`
Will generate a list of secrets with their secretId, userName, deviceCategory and deviceName fields, but not secret data. Only secrets belonging to safes that are accessible by the currently logged in user will be returned
#### Delete Secret
**POST** `/api/secret/delete`
Body
```
{
"deviceName": "device.example.com",
"deviceCategory": "",
"userName": "example-user"
}
```
Body
```
{
"secretId": 29
}
```
Deletes specified secret. User must have read-write access to the safe the secret is stored in.
Secret can be specified either by the secretId, or a unique combination of deviceName, deviceCategory and userName.
## Database Schema
![Diagram](www/database.png)

View File

@@ -17,9 +17,10 @@ import (
type AddUserInput struct {
UserName string `json:"userName" binding:"required"`
Password string `json:"password" binding:"required"`
Password string `json:"password"`
GroupId int `json:"groupId"`
GroupName string `json:"groupName"`
LdapUser bool `json:"ldapUser"`
//RoleId int `json:"roleid"`
}
@@ -80,9 +81,10 @@ func DeleteUser(c *gin.Context) {
// Create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Deleted User Id %d", testUser.UserId),
}
a.AuditAdd()
a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "user deletion success"})
}
@@ -98,12 +100,17 @@ func AddUser(c *gin.Context) {
}
if len(input.UserName) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified"})
c.JSON(http.StatusBadRequest, gin.H{"error": "username must be specified"})
return
}
if len(input.Password) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "no password specified"})
if len(input.Password) == 0 && !input.LdapUser {
c.JSON(http.StatusBadRequest, gin.H{"error": "password must be specified for non-ldap user"})
return
}
if input.LdapUser && len(input.Password) > 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "password should not be specified for ldap user"})
return
}
@@ -117,6 +124,7 @@ func AddUser(c *gin.Context) {
u := models.User{}
u.UserName = input.UserName
u.Password = input.Password
u.LdapUser = input.LdapUser
// Determine which GroupId to save
// Can be specified either by GroupName or GroupId in the request
@@ -160,18 +168,20 @@ func AddUser(c *gin.Context) {
return
}
//turn password into hash
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"Error hashing password": err.Error()})
return
} else {
//log.Printf("Register generated hashed password value '%s' from '%s'\n", string(hashedPassword), input.Password)
log.Printf("Register generated hashed password value '%s'\n", string(hashedPassword))
//turn password into hash if defined
if len(input.Password) > 0 {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"Error hashing password": err.Error()})
return
} else {
//log.Printf("Register generated hashed password value '%s' from '%s'\n", string(hashedPassword), input.Password)
log.Printf("Register generated hashed password value '%s'\n", string(hashedPassword))
}
u.Password = string(hashedPassword)
}
u.Password = string(hashedPassword)
_, err = u.SaveUser()
_, err := u.SaveUser()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"Error saving user": err.Error()})
@@ -181,9 +191,10 @@ func AddUser(c *gin.Context) {
// Create audit record
a := models.Audit{
UserId: RequestingUserId,
EventText: fmt.Sprintf("Created User Id %d", u.UserId),
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Created User '%s' with id %d", u.UserName, u.UserId),
}
a.AuditAdd()
a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "user registration success", "data": u})
}
@@ -210,7 +221,8 @@ func Login(c *gin.Context) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "username or password is incorrect."})
return
} else {
log.Printf("Login verified, returning token '%s'\n", token)
//log.Printf("Login verified, returning token '%s'\n", token)
log.Printf("Login verified, returning token\n")
}
c.JSON(http.StatusOK, gin.H{"access_token": token})

View File

@@ -34,6 +34,7 @@ func GetGroupsHandler(c *gin.Context) {
func AddGroupHandler(c *gin.Context) {
var input GroupInput
var RequestingUserId int
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -50,6 +51,13 @@ func AddGroupHandler(c *gin.Context) {
return
}
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
RequestingUserId = val.(int)
}
g := models.Group{}
g.GroupName = input.GroupName
g.LdapGroup = input.LdapGroup
@@ -90,6 +98,14 @@ func AddGroupHandler(c *gin.Context) {
// Verification checks passed, return group
group, err := g.GroupAdd()
// Create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Created Group '%s' with id %d", g.GroupName, g.GroupId),
}
a.AuditLogAdd()
if err != nil {
errString := fmt.Sprintf("error creating group : '%s'", err)
log.Printf("AddGroupHandler %s\n", errString)
@@ -102,6 +118,7 @@ func AddGroupHandler(c *gin.Context) {
func DeleteGroupHandler(c *gin.Context) {
var input GroupInput
var RequestingUserId int
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -114,6 +131,13 @@ func DeleteGroupHandler(c *gin.Context) {
return
}
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
RequestingUserId = val.(int)
}
g := models.Group{}
g.GroupId = input.GroupId
g.GroupName = input.GroupName
@@ -152,6 +176,14 @@ func DeleteGroupHandler(c *gin.Context) {
err := g.GroupDelete()
// Create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Deleted Group '%s' with id %d", g.GroupName, g.GroupId),
}
a.AuditLogAdd()
if err != nil {
errString := fmt.Sprintf("error deleting group : '%s'", err)
log.Printf("DeleteGroupHandler %s\n", errString)

View File

@@ -6,6 +6,7 @@ import (
"log"
"net/http"
"smt/models"
"smt/utils"
"strings"
"github.com/gin-gonic/gin"
@@ -35,6 +36,7 @@ func GetPermissionsHandler(c *gin.Context) {
func AddPermissionHandler(c *gin.Context) {
var input PermissionInput
var RequestingUserId int
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -55,6 +57,13 @@ func AddPermissionHandler(c *gin.Context) {
return
}
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
RequestingUserId = val.(int)
}
p := models.Permission{
PermissionId: input.PermissionId,
Description: input.Description,
@@ -82,6 +91,14 @@ func AddPermissionHandler(c *gin.Context) {
_, err := p.PermissionAdd()
// Create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Created Permission '%s' with id %d on safe id %d for group id %d or user id %d", p.Description, p.PermissionId, p.SafeId, p.GroupId, p.UserId),
}
a.AuditLogAdd()
if err != nil {
errString := fmt.Sprintf("error creating permission : '%s'", err)
log.Printf("AddPermissionHandler %s\n", errString)
@@ -94,6 +111,7 @@ func AddPermissionHandler(c *gin.Context) {
func DeletePermissionHandler(c *gin.Context) {
var input PermissionInput
var RequestingUserId int
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -108,6 +126,13 @@ func DeletePermissionHandler(c *gin.Context) {
return
}
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
RequestingUserId = val.(int)
}
p := models.Permission{
PermissionId: input.PermissionId,
Description: input.Description,
@@ -121,23 +146,122 @@ func DeletePermissionHandler(c *gin.Context) {
p.Description = html.EscapeString(strings.TrimSpace(p.Description))
// Check if permission definition already exists
testPermission, _ := models.PermissionGetByDesc(p.Description)
log.Printf("DeletePermissionHandler confirming permission with description '%s' exists\n", p.Description)
if (models.Permission{} == testPermission) {
errString := fmt.Sprintf("attempt to delete non-existing permission with description '%s'", p.Description)
log.Printf("DeletePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
} else {
err := p.PermissionDelete()
if len(p.Description) > 0 {
log.Printf("DeletePermissionHandler confirming permission with description '%s' exists\n", p.Description)
testPermission, _ := models.PermissionGetByDesc(p.Description)
if err != nil {
errString := fmt.Sprintf("error deleting permission : '%s'", err)
if (models.Permission{} == testPermission) {
errString := fmt.Sprintf("attempt to delete non-existing permission with description '%s'", p.Description)
log.Printf("DeletePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
} else {
log.Printf("DeletePermissionHandler confirming permission with id '%d' exists\n", p.PermissionId)
testPermission, _ := models.PermissionGetById(p.PermissionId)
c.JSON(http.StatusOK, gin.H{"message": "permission deletion success"})
if (models.Permission{} == testPermission) {
errString := fmt.Sprintf("attempt to delete non-existing permission with id '%d'", p.PermissionId)
log.Printf("DeletePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
}
err := p.PermissionDelete()
// Create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Deleted Permission '%s' with id %d", p.Description, p.PermissionId),
}
a.AuditLogAdd()
if err != nil {
errString := fmt.Sprintf("error deleting permission : '%s'", err)
log.Printf("DeletePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
c.JSON(http.StatusOK, gin.H{"message": "permission deletion success"})
}
func UpdatePermissionHandler(c *gin.Context) {
var input PermissionInput
var RequestingUserId int
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Input validation
if input.PermissionId == 0 {
errString := "must specify permission id"
log.Printf("UpdatePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
} else {
RequestingUserId = val.(int)
}
// Check specified permission currently exists
currentPermission, err := models.PermissionGetById(input.PermissionId)
if err != nil {
errString := fmt.Sprintf("error querying existing permission : '%s'", err)
log.Printf("UpdatePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
if (models.Permission{} == currentPermission) {
errString := fmt.Sprintf("no permission id '%d' found", input.PermissionId)
log.Printf("UpdatePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
// create new struct with values supplied by user
newPermission := models.Permission{
PermissionId: input.PermissionId,
Description: input.Description,
ReadOnly: input.ReadOnly,
SafeId: input.SafeId,
UserId: input.UserId,
GroupId: input.GroupId,
}
//remove leading/trailing spaces in permission description
newPermission.Description = html.EscapeString(strings.TrimSpace(newPermission.Description))
// Copy newPermission into currentPermission
utils.UpdateStruct(&currentPermission, &newPermission)
// run the database update
_, err = currentPermission.PermissionUpdate()
if err != nil {
errString := fmt.Sprintf("error updating permission : '%s'", err)
log.Printf("UpdatePermissionHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
//create audit record
a := models.Audit{
UserId: RequestingUserId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Updated Permission '%s' with id %d", currentPermission.Description, currentPermission.PermissionId),
}
a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "permission update success", "data": currentPermission})
}

View File

@@ -0,0 +1,23 @@
package controllers
import (
"fmt"
"log"
"net/http"
"smt/models"
"github.com/gin-gonic/gin"
)
func GetAuditLogsHandler(c *gin.Context) {
logs, err := models.AuditLogList()
if err != nil {
errString := fmt.Sprintf("error retrieving audit logs : '%s'", err)
log.Printf("GetAuditLogsHandler %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
c.JSON(http.StatusOK, gin.H{"message": "success", "data": logs})
}

View File

@@ -5,11 +5,13 @@ import (
"log"
"net/http"
"smt/models"
"time"
"github.com/gin-gonic/gin"
)
type RetrieveInput struct {
SecretId int `json:"secretId"`
DeviceName string `json:"deviceName"`
DeviceCategory string `json:"deviceCategory"`
UserName string `json:"userName"`
@@ -18,42 +20,43 @@ type RetrieveInput struct {
}
type ListSecret struct {
SecretId int `db:"SecretId" json:"secretId"`
SafeId int `db:"SafeId" json:"safeId"`
DeviceName string `db:"DeviceName" json:"deviceName"`
DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"`
UserName string `db:"UserName" json:"userName"`
Secret string `db:"Secret" json:"-"`
SecretId int `db:"SecretId" json:"secretId"`
SafeId int `db:"SafeId" json:"safeId"`
DeviceName string `db:"DeviceName" json:"deviceName"`
DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"`
UserName string `db:"UserName" json:"userName"`
Secret string `db:"Secret" json:"-"`
LastUpdated time.Time `db:"LastUpdated" json:"lastUpdated"`
}
func RetrieveSecret(c *gin.Context) {
var input RetrieveInput
//var results []models.Secret
//var userIsAdmin bool = false
// Validate the input matches our struct
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
log.Printf("RetrieveSecret received JSON input '%v'\n", input)
/*
// Get the user and role id of the requestor
u, err := models.UserGetRoleFromToken(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
*/
//log.Printf("RetrieveSecret received JSON input '%v'\n", input)
// Populate fields
s := models.Secret{}
//s.RoleId = u.RoleId
s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory
s.UserName = input.UserName
if input.SecretId > 0 {
s.SecretId = input.SecretId
}
if input.DeviceName == "" && input.DeviceCategory == "" && input.UserName == "" && input.SecretId == 0 {
errString := "no values provided to select secret"
log.Printf("RetrieveSecret %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
retrieveSpecifiedSecret(&s, c)
}
@@ -61,7 +64,9 @@ func RetrieveSecretByDevicename(c *gin.Context) {
DeviceName := c.Param("devicename")
if DeviceName == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "no devicename value specified"})
errString := "no devicename value specified"
log.Printf("RetrieveSecretByDevicename %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
@@ -76,7 +81,9 @@ func RetrieveSecretByDevicecategory(c *gin.Context) {
DeviceCategory := c.Param("devicecategory")
if DeviceCategory == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "no devicecategory value specified"})
errString := "no devicecategory value specified"
log.Printf("RetrieveSecretByDevicecategory %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
@@ -86,34 +93,31 @@ func RetrieveSecretByDevicecategory(c *gin.Context) {
retrieveSpecifiedSecret(&s, c)
}
func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) {
/*
// Get the user and role id of the requestor
u, err := models.UserGetRoleFromToken(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
s.RoleId = u.RoleId
func RetrieveSecretByUsername(c *gin.Context) {
userName := c.Param("username")
results, err := models.GetSecrets(s, false)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
*/
if userName == "" {
errString := "no username value specified"
log.Printf("RetrieveSecretByUsername %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
// Create object based on specified data
s := models.Secret{UserName: userName}
retrieveSpecifiedSecret(&s, c)
}
func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) {
var UserId int
var results []models.Secret
/*
user_id, err := token.ExtractTokenID(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
return
}
*/
// Get userId that we stored in the context earlier
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
errString := "error determining user"
log.Printf("retrieveSpecifiedSecret %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
} else {
UserId = val.(int)
@@ -123,7 +127,9 @@ func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) {
safeList, err := models.UserGetSafesAllowed(int(UserId))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user safes"})
errString := "error determining user safes"
log.Printf("retrieveSpecifiedSecret %s\n", errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
@@ -151,29 +157,29 @@ func retrieveSpecifiedSecret(s *models.Secret, c *gin.Context) {
return
}
if len(results) == 1 {
// Create audit record
a := models.Audit{
UserId: UserId,
EventText: fmt.Sprintf("Retrieved Secret Id %d", results[0].SecretId),
}
a.AuditAdd()
// output results as json
c.JSON(http.StatusOK, gin.H{"message": "success", "data": results})
} else if len(results) > 1 {
c.JSON(http.StatusBadRequest, gin.H{"error": "found multiple matching secrets"})
return
} else {
if len(results) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "found no matching secrets"})
return
}
// Create audit record for results
for i := range results {
a := models.Audit{
UserId: UserId,
SecretId: results[i].SecretId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("User '%s' retrieved SecretId %d", safeList[0].User.UserName, results[i].SecretId),
}
a.AuditLogAdd()
}
// output results as json
c.JSON(http.StatusOK, gin.H{"message": "success", "data": results, "count": len(results)})
}
func ListSecrets(c *gin.Context) {
var UserId int
var output []ListSecret
var results []ListSecret
//var results []models.Secret
s := models.Secret{}
@@ -196,61 +202,18 @@ func ListSecrets(c *gin.Context) {
// Extract the normal secret fields from the allowed list
for _, secret := range secretList {
output = append(output, ListSecret(secret.Secret))
results = append(results, ListSecret(secret.Secret))
}
// Create audit record
a := models.Audit{
UserId: UserId,
EventText: fmt.Sprintf("Listed %d secrets, %+v", len(output), s),
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Listed %d secrets accessible to user", len(results)),
}
a.AuditAdd()
a.AuditLogAdd()
// output results as json
c.JSON(http.StatusOK, gin.H{"message": "success", "data": output})
c.JSON(http.StatusOK, gin.H{"message": "success", "data": results, "count": len(results)})
}
func RetrieveMultpleSecrets(c *gin.Context) {
// TODO implement with new schema
/*
var input RetrieveInput
// Validate the input matches our struct
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
log.Printf("StoreSecret received JSON input '%v'\n", input)
// Get the user and role id of the requestor
user_id, err := token.ExtractTokenID(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
u, err := models.GetUserRoleByID(user_id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Populate fields
s := models.Secret{}
s.RoleId = u.RoleId
s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory
results, err := models.GetSecrets(&s, false)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// output results as json
c.JSON(http.StatusOK, gin.H{"message": "success", "data": results})
*/
}

View File

@@ -14,6 +14,7 @@ import (
type SecretInput struct {
SafeId int `json:"safeId"`
SafeName string `json:"safeName"`
SecretId int `json:"secretId"`
DeviceName string `json:"deviceName"`
DeviceCategory string `json:"deviceCategory"`
UserName string `json:"userName"`
@@ -146,9 +147,11 @@ func StoreSecret(c *gin.Context) {
// Create audit record
a := models.Audit{
UserId: UserId,
SecretId: s.SecretId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Created Secret Id %d", s.SecretId),
}
a.AuditAdd()
a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "secret stored successfully", "data": models.SecretRestricted(s)})
}
@@ -254,16 +257,6 @@ func UpdateSecret(c *gin.Context) {
return
}
// Temporarily disable because we should be able to figure it out without user specifying
/*
if input.SafeId == 0 && len(input.SafeName) == 0 {
errString := "UpdateSecret no safe specified\n"
log.Print(errString)
c.JSON(http.StatusBadRequest, gin.H{"error": errString})
return
}
*/
// Get userId that we stored in the context earlier
if val, ok := c.Get("user-id"); !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "error determining user"})
@@ -367,9 +360,11 @@ func UpdateSecret(c *gin.Context) {
// Create audit record
a := models.Audit{
UserId: UserId,
SecretId: s.SecretId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Updated Secret Id %d", s.SecretId),
}
a.AuditAdd()
a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "secret updated successfully", "data": models.SecretRestricted(s)})
} else {
@@ -402,6 +397,10 @@ func DeleteSecret(c *gin.Context) {
// Populate fields
s := models.Secret{}
if input.SecretId > 0 {
s.SecretId = input.SecretId
}
s.UserName = input.UserName
s.DeviceName = input.DeviceName
s.DeviceCategory = input.DeviceCategory
@@ -453,9 +452,11 @@ func DeleteSecret(c *gin.Context) {
// Create audit record
a := models.Audit{
UserId: UserId,
SecretId: s.SecretId,
IpAddress: c.ClientIP(),
EventText: fmt.Sprintf("Deleted Secret Id %d", s.SecretId),
}
a.AuditAdd()
a.AuditLogAdd()
c.JSON(http.StatusOK, gin.H{"message": "secret deleted successfully"})
} else {

77
go.mod
View File

@@ -1,54 +1,57 @@
module smt
go 1.19
go 1.24.4
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.9.0
github.com/go-ldap/ldap v3.0.3+incompatible
github.com/jmoiron/sqlx v1.3.5
github.com/gin-gonic/gin v1.10.1
github.com/go-ldap/ldap/v3 v3.4.11
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/jmoiron/sqlx v1.4.0
github.com/joho/godotenv v1.5.1
golang.org/x/crypto v0.13.0
modernc.org/sqlite v1.21.0
golang.org/x/crypto v0.39.0
modernc.org/sqlite v1.38.0
)
require (
github.com/bytedance/sonic v1.8.6 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.12.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.2 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
golang.org/x/arch v0.18.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.3 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
modernc.org/gc/v3 v3.1.0 // indirect
modernc.org/libc v1.66.2 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/strutil v1.2.1 // indirect
modernc.org/token v1.1.0 // indirect
)

265
go.sum
View File

@@ -1,139 +1,216 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.8.6 h1:aUgO9S8gvdN6SyW2EhIpAw5E4ChworywIEndZCkCVXk=
github.com/bytedance/sonic v1.8.6/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24=
github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.20.7 h1:skrinQsjxWfvj6nbC3ztZPJy+NuwmB3hV9zX/pthNYQ=
modernc.org/ccgo/v4 v4.20.7/go.mod h1:UOkI3JSG2zT4E2ioHlncSOZsXbuDCZLvPi3uMlZT5GY=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a h1:CfbpOLEo2IwNzJdMvE8aiRbPMxoTpgAJeyePh0SmO8M=
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/gc/v3 v3.1.0 h1:CiObI+9ROz7pjjH3iAgMPaFCN5zE3sN5KF4jet8BWdc=
modernc.org/gc/v3 v3.1.0/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/libc v1.59.9 h1:k+nNDDakwipimgmJ1D9H466LhFeSkaPPycAs1OZiDmY=
modernc.org/libc v1.59.9/go.mod h1:EY/egGEU7Ju66eU6SBqCNYaFUDuc4npICkMWnU5EE3A=
modernc.org/libc v1.66.2 h1:JCBxlJzZOIwZY54fzjHN3Wsn8Ty5PUTPr/xioRkmecI=
modernc.org/libc v1.66.2/go.mod h1:ceIGzvXxP+JV3pgVjP9avPZo6Chlsfof2egXBH3YT5Q=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow=
modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

18
main.go
View File

@@ -11,6 +11,7 @@ import (
"net/http"
"os"
"os/signal"
"runtime"
"smt/controllers"
"smt/middlewares"
"smt/models"
@@ -100,6 +101,7 @@ func main() {
// These replacements are for the embedded html generated from README.md
replacements["{SHA1VER}"] = sha1ver
replacements["{BUILDTIME}"] = buildTime
replacements["{RUNTIME}"] = runtime.Version()
// Load data from environment file
envFilename := utils.GetFilePath(".env")
@@ -121,7 +123,7 @@ func main() {
}
log.SetOutput(logfileWriter)
log.Printf("SMT starting execution. Built on %s from sha1 %s\n", buildTime, sha1ver)
log.Printf("SMT starting execution. Built on %s from sha1 %s. Runtime %s\n", buildTime, sha1ver, runtime.Version())
/*
// for debugging, list all the files that we embedded at compile time
@@ -199,7 +201,7 @@ func main() {
// Determine bind port
bindPort := os.Getenv("BIND_PORT")
if bindPort == "" {
bindIP = "8443"
bindPort = "8443"
}
bindAddress := fmt.Sprint(bindIP, ":", bindPort)
log.Printf("Will listen on address 'https://%s'\n", bindAddress)
@@ -248,19 +250,20 @@ func main() {
adminOnly.POST("/user/add", controllers.AddUser)
adminOnly.GET("/users", controllers.GetUsers)
// TODO
//adminOnly.POST("/user/update", controllers.UpdateUser)
//adminOnly.POST("/user/update", controllers.UpdateUserHandler)
// Group functions for admin
adminOnly.GET("/groups", controllers.GetGroupsHandler)
adminOnly.POST("/group/add", controllers.AddGroupHandler)
// TODO
//adminOnly.POST("/group/update", controllers.UpdateGroup)
//adminOnly.POST("/group/update", controllers.UpdateGroupHandler)
adminOnly.POST("/group/delete", controllers.DeleteGroupHandler)
// Permission functions for admin
adminOnly.GET("/permissions", controllers.GetPermissionsHandler)
adminOnly.POST("/permission/add", controllers.AddPermissionHandler)
adminOnly.POST("/permission/delete", controllers.DeletePermissionHandler)
adminOnly.POST("/permission/update", controllers.UpdatePermissionHandler)
// Safe functions for admin
adminOnly.GET("/safe/listall", controllers.GetAllSafesHandler)
@@ -269,6 +272,10 @@ func main() {
// Other functions for admin
adminOnly.POST("/unlock", controllers.Unlock)
adminOnly.GET("/logs", controllers.GetAuditLogsHandler)
// TODO
//adminOnly.GET("/logs/secret/:id", controllers.GetAuditLogsBySecretHandler)
//adminOnly.GET("/logs/user/:id", controllers.GetAuditLogsByUserHandler)
// Get secrets
secretRoutes := router.Group("/api/secret")
@@ -276,12 +283,10 @@ func main() {
secretRoutes.POST("/retrieve", controllers.RetrieveSecret) // TODO deprecate, replace retrieve with get
secretRoutes.POST("/get", controllers.RetrieveSecret)
secretRoutes.GET("/list", controllers.ListSecrets)
//secretRoutes.POST("/retrieveMultiple", controllers.RetrieveMultpleSecrets) // TODO is this still required?
secretRoutes.POST("/store", controllers.StoreSecret) // TODO deprecate, replace store with add
secretRoutes.POST("/add", controllers.StoreSecret)
secretRoutes.POST("/update", controllers.UpdateSecret)
// TODO
secretRoutes.POST("/delete", controllers.DeleteSecret)
// Get Safes (only those user allowed to access)
@@ -293,6 +298,7 @@ func main() {
// See https://gin-gonic.com/docs/examples/param-in-path/
secretRoutes.GET("/retrieve/name/:devicename", controllers.RetrieveSecretByDevicename)
secretRoutes.GET("/retrieve/category/:devicecategory", controllers.RetrieveSecretByDevicecategory)
secretRoutes.GET("/retrieve/user/:username", controllers.RetrieveSecretByUsername)
// Initializing the server in a goroutine so that
// it won't block the graceful shutdown handling below

View File

@@ -12,10 +12,11 @@ type Audit struct {
SecretId int `db:"SecretId" json:"secretId"`
EventText string `db:"EventText" json:"eventText"`
EventTime time.Time `db:"EventTime" json:"eventTime"`
IpAddress string `db:"IpAddress" json:"ipAddress"`
}
// AuditAdd adds a new audit record to the database
func (a *Audit) AuditAdd() (*Audit, error) {
// AuditLogAdd adds a new audit record to the database
func (a *Audit) AuditLogAdd() (*Audit, error) {
var err error
// Populate timestamp field if not already set
@@ -23,17 +24,45 @@ func (a *Audit) AuditAdd() (*Audit, error) {
a.EventTime = time.Now().UTC()
}
result, err := db.NamedExec(("INSERT INTO audit (UserId, SecretId, EventText, EventTime) VALUES (:UserId, :SecretId, :EventText, :EventTime);"), a)
result, err := db.NamedExec(("INSERT INTO audit (UserId, SecretId, EventText, EventTime, IpAddress) VALUES (:UserId, :SecretId, :EventText, :EventTime, :IpAddress);"), a)
if err != nil {
log.Printf("AuditAdd error executing sql record : '%s'\n", err)
log.Printf("AuditLogAdd error executing sql record : '%s'\n", err)
return &Audit{}, err
} else {
affected, _ := result.RowsAffected()
id, _ := result.LastInsertId()
a.AuditId = int(id)
log.Printf("AuditAdd insert returned result id '%d' affecting %d row(s).\n", id, affected)
log.Printf("AuditLogAdd insert returned result id '%d' affecting %d row(s).\n", id, affected)
}
return a, nil
}
// AuditList returns a list of all audit logs in database
func AuditLogList() ([]Audit, error) {
var results []Audit
// Query database for groups
rows, err := db.Queryx("SELECT * FROM audit")
if err != nil {
log.Printf("AuditLogList error executing sql record : '%s'\n", err)
return results, err
} else {
// parse all the results into a slice
for rows.Next() {
var a Audit
err = rows.StructScan(&a)
if err != nil {
log.Printf("AuditLogList error parsing sql record : '%s'\n", err)
return results, err
}
results = append(results, a)
}
log.Printf("AuditLogList retrieved '%d' results\n", len(results))
}
return results, nil
}

View File

@@ -27,7 +27,8 @@ const createUsers string = `
UserName VARCHAR,
Password VARCHAR,
Admin BOOLEAN DEFAULT 0,
LdapUser BOOLEAN DEFAULT 0
LdapUser BOOLEAN DEFAULT 0,
LastLogin datetime DEFAULT (datetime('1970-01-01 00:00:00'))
);
`
@@ -68,6 +69,7 @@ const createSecrets string = `
DeviceCategory VARCHAR,
UserName VARCHAR,
Secret VARCHAR,
LastUpdated datetime DEFAULT (datetime('1970-01-01 00:00:00')),
FOREIGN KEY (SafeId) REFERENCES safes(SafeId)
);
`
@@ -83,8 +85,9 @@ const createAudit string = `
AuditId INTEGER PRIMARY KEY AUTOINCREMENT,
UserId INTEGER DEFAULT 0,
SecretId INTEGER DEFAULT 0,
EventText VARCHAR,
EventTime datetime
EventText VARCHAR DEFAULT '',
IpAddress VARCHAR DEFAULT '',
EventTime datetime DEFAULT (datetime('1970-01-01 00:00:00'))
);
`
@@ -119,29 +122,8 @@ func DisconnectDatabase() {
func CreateTables() {
var err error
var rowCount int
// Create database tables if it doesn't exist
/*
// Roles table should go first since other tables refer to it
if _, err = db.Exec(createRoles); err != nil {
log.Printf("Error checking roles table : '%s'", err)
os.Exit(1)
}
rowCount, _ = CheckCount("roles")
if rowCount == 0 {
if _, err = db.Exec("INSERT INTO roles VALUES(1, 'Admin', false);"); err != nil {
log.Printf("Error adding initial admin role : '%s'", err)
os.Exit(1)
}
if _, err = db.Exec("INSERT INTO roles VALUES(2, 'UserRole', false);"); err != nil {
log.Printf("Error adding initial user role : '%s'", err)
os.Exit(1)
}
if _, err = db.Exec("INSERT INTO roles VALUES(3, 'GuestRole', true);"); err != nil {
log.Printf("Error adding initial guest role : '%s'", err)
os.Exit(1)
}
}
*/
// groups table
if _, err = db.Exec(createGroups); err != nil {
@@ -247,11 +229,16 @@ func CreateTables() {
os.Exit(1)
}
// Remove users RoleId column
userRoleIdCheck, _ := CheckColumnExists("users", "RoleId")
if userRoleIdCheck {
//_, err := db.Exec("ALTER TABLE users DROP COLUMN RoleId;")
_, err := db.Exec(`
// Check the database schema version
version, _ := GetSchemaVersion()
if version >= 3 {
log.Printf("Database schema up to date\n")
} else {
// Remove users RoleId column
userRoleIdCheck, _ := CheckColumnExists("users", "RoleId")
if userRoleIdCheck {
//_, err := db.Exec("ALTER TABLE users DROP COLUMN RoleId;")
_, err := db.Exec(`
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE users RENAME TO _users_old;
@@ -269,43 +256,49 @@ func CreateTables() {
PRAGMA foreign_keys=on;
DROP TABLE _users_old;
`)
if err != nil {
log.Printf("Error altering users table to drop RoleId column : '%s'\n", err)
if err != nil {
log.Printf("Error altering users table to drop RoleId column : '%s'\n", err)
os.Exit(1)
}
}
// Set any unassigned secrets to the default safe id
if _, err = db.Exec("UPDATE users SET LdapUser = 0 WHERE LdapUser is null;"); err != nil {
log.Printf("Error setting LdapUser flag to false for existing users : '%s'", err)
os.Exit(1)
}
}
// Remove LdapGroup column from roles table
ldapCheck, _ := CheckColumnExists("roles", "LdapGroup")
if ldapCheck {
_, err := db.Exec("ALTER TABLE roles DROP COLUMN LdapGroup;")
if err != nil {
log.Printf("Error altering roles table to renmove LdapGroup column : '%s'\n", err)
// Remove LdapGroup column from roles table
ldapCheck, _ := CheckColumnExists("roles", "LdapGroup")
if ldapCheck {
_, err := db.Exec("ALTER TABLE roles DROP COLUMN LdapGroup;")
if err != nil {
log.Printf("Error altering roles table to renmove LdapGroup column : '%s'\n", err)
os.Exit(1)
}
}
// Add SafeId column to secrets table
safeIdCheck, _ := CheckColumnExists("secrets", "SafeId")
if !safeIdCheck {
// Add the column for LdapGroup in the roles table
_, err := db.Exec("ALTER TABLE secrets ADD COLUMN SafeId INTEGER REFERENCES safes(SafeId);")
if err != nil {
log.Printf("Error altering secrets table to add SafeId column : '%s'\n", err)
os.Exit(1)
}
}
// Set any unassigned secrets to the default safe id
if _, err = db.Exec("UPDATE secrets SET SafeId = 1 WHERE SafeId is null;"); err != nil {
log.Printf("Error setting safe ID of existing secrets : '%s'", err)
os.Exit(1)
}
}
// Add SafeId column to secrets table
safeIdCheck, _ := CheckColumnExists("secrets", "SafeId")
if !safeIdCheck {
// Add the column for LdapGroup in the roles table
_, err := db.Exec("ALTER TABLE secrets ADD COLUMN SafeId INTEGER REFERENCES safes(SafeId);")
if err != nil {
log.Printf("Error altering secrets table to add SafeId column : '%s'\n", err)
os.Exit(1)
}
}
// Set any unassigned secrets to the default safe id
if _, err = db.Exec("UPDATE secrets SET SafeId = 1 WHERE SafeId is null;"); err != nil {
log.Printf("Error setting safe ID of existing secrets : '%s'", err)
os.Exit(1)
}
// Remove RoleId column from secrets table
secretsRoleIdCheck, _ := CheckColumnExists("secrets", "RoleId")
if secretsRoleIdCheck {
_, err := db.Exec(`
// Remove RoleId column from secrets table
secretsRoleIdCheck, _ := CheckColumnExists("secrets", "RoleId")
if secretsRoleIdCheck {
_, err := db.Exec(`
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE secrets RENAME TO _secrets_old;
@@ -322,30 +315,32 @@ func CreateTables() {
);
INSERT INTO secrets SELECT SecretId, RoleId, SafeId, DeviceName, DeviceCategory, UserName, Secret FROM _secrets_old;
ALTER TABLE secrets DROP COLUMN RoleId;
ALTER TABLE secrets ADD COLUMN LastUpdated datetime;
UPDATE secrets SET LastUpdated = (datetime('1970-01-01 00:00:00')) WHERE LastUpdated is null;
COMMIT;
PRAGMA foreign_keys=on;
DROP TABLE _secrets_old;
`)
if err != nil {
log.Printf("Error altering secrets table to remove RoleId column : '%s'\n", err)
os.Exit(1)
if err != nil {
log.Printf("Error altering secrets table to remove RoleId column : '%s'\n", err)
os.Exit(1)
}
}
}
// Remove the Admin column from roles table
rolesAdminCheck, _ := CheckColumnExists("roles", "Admin")
if rolesAdminCheck {
_, err := db.Exec("ALTER TABLE roles DROP COLUMN Admin;")
if err != nil {
log.Printf("Error altering roles table to remove Admin column : '%s'\n", err)
os.Exit(1)
// Remove the Admin column from roles table
rolesAdminCheck, _ := CheckColumnExists("roles", "Admin")
if rolesAdminCheck {
_, err := db.Exec("ALTER TABLE roles DROP COLUMN Admin;")
if err != nil {
log.Printf("Error altering roles table to remove Admin column : '%s'\n", err)
os.Exit(1)
}
}
}
// Remove the RoleId from permissiosn table
permissionsRoleIdCheck, _ := CheckColumnExists("permissions", "RoleId")
if permissionsRoleIdCheck {
_, err := db.Exec(`
// Remove the RoleId from permissiosn table
permissionsRoleIdCheck, _ := CheckColumnExists("permissions", "RoleId")
if permissionsRoleIdCheck {
_, err := db.Exec(`
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE permissions RENAME TO _permissions_old;
@@ -368,43 +363,75 @@ func CreateTables() {
PRAGMA foreign_keys=on;
DROP TABLE _permissions_old;
`)
if err != nil {
log.Printf("Error altering permissions table to remove RoleId column : '%s'\n", err)
os.Exit(1)
if err != nil {
log.Printf("Error altering permissions table to remove RoleId column : '%s'\n", err)
os.Exit(1)
}
}
secretsLastUpdatedCheck, _ := CheckColumnExists("secrets", "LastUpdated")
if !secretsLastUpdatedCheck {
// Add the column for LastUpdated in the secrets table
_, err := db.Exec("ALTER TABLE secrets ADD COLUMN LastUpdated datetime;")
if err != nil {
log.Printf("Error altering secrets table to add LastUpdated column : '%s'\n", err)
os.Exit(1)
}
// Set the default value
if _, err = db.Exec("UPDATE secrets SET LastUpdated = (datetime('1970-01-01 00:00:00')) WHERE LastUpdated is null;"); err != nil {
log.Printf("Error setting LastUpdated of existing secrets : '%s'", err)
os.Exit(1)
}
}
lastLoginCheck, _ := CheckColumnExists("users", "LastLogin")
if !lastLoginCheck {
// Add the column for LastUpdated in the secrets table
_, err := db.Exec("ALTER TABLE users ADD COLUMN LastLogin datetime;")
if err != nil {
log.Printf("Error altering users table to add LastLogin column : '%s'\n", err)
os.Exit(1)
}
// Set the default value
if _, err = db.Exec("UPDATE users SET LastLogin = (datetime('1970-01-01 00:00:00')) WHERE LastLogin is null;"); err != nil {
log.Printf("Error setting LastLogin of existing users : '%s'", err)
os.Exit(1)
}
}
// Add IpAddress column to audit table
auditIPCheck, _ := CheckColumnExists("audit", "IpAddress")
if !auditIPCheck {
// Add the column for LdapGroup in the roles table
_, err := db.Exec("ALTER TABLE audit ADD COLUMN IpAddress VARCHAR;")
if err != nil {
log.Printf("Error altering audit table to add IpAddress column : '%s'\n", err)
os.Exit(1)
}
if _, err = db.Exec("UPDATE audit SET IpAddress = '' WHERE IpAddress is null;"); err != nil {
log.Printf("Error setting IpAddress of existing audit records : '%s'", err)
os.Exit(1)
}
}
// Set the schema version
rowCount, _ = CheckCount("schema")
if rowCount > 0 {
if _, err = db.Exec("UPDATE schema SET Version = 3;"); err != nil {
log.Printf("Error setting schema to version 3 : '%s'", err)
os.Exit(1)
}
} else {
if _, err = db.Exec("INSERT INTO schema (Version) VALUES (3);"); err != nil {
log.Printf("Error setting schema to version 3 : '%s'", err)
os.Exit(1)
}
}
}
/*
// Database updates added after initial version released
ldapCheck, _ := CheckColumnExists("roles", "LdapGroup")
if !ldapCheck {
// Add the column for LdapGroup in the roles table
_, err := db.Exec("ALTER TABLE roles ADD COLUMN LdapGroup VARCHAR DEFAULT '';")
if err != nil {
log.Printf("Error altering roles table to add LdapGroup column : '%s'\n", err)
os.Exit(1)
}
}
// Add the two LDAP columns to the users table if they weren't there
ldapUserCheck, _ := CheckColumnExists("users", "LdapUser")
if !ldapUserCheck {
log.Printf("CreateTables creating ldap columns in user table")
_, err := db.Exec("ALTER TABLE users ADD COLUMN LdapUser BOOLEAN DEFAULT 0;")
if err != nil {
log.Printf("Error altering users table to add LdapUser column : '%s'\n", err)
os.Exit(1)
}
_, err = db.Exec("ALTER TABLE users ADD COLUMN LdapDn VARCHAR DEFAULT '';")
if err != nil {
log.Printf("Error altering users table to add LdapDn column : '%s'\n", err)
os.Exit(1)
}
}
*/
}
// Count the number of records in the sqlite database
@@ -425,6 +452,23 @@ func CheckCount(tablename string) (int, error) {
return count, nil
}
func GetSchemaVersion() (int, error) {
var version int
stmt, err := db.Prepare("SELECT Version FROM schema;")
if err != nil {
log.Printf("GetSchemaVersion error preparing sqlite statement : '%s'\n", err)
return 0, err
}
err = stmt.QueryRow().Scan(&version)
if err != nil {
log.Printf("GetSchemaVersion error querying database record count : '%s'\n", err)
return 0, err
}
stmt.Close() // or use defer rows.Close(), idc
return version, nil
}
// From https://stackoverflow.com/a/60100045
func GenerateInsertMethod(q interface{}) (string, error) {
if reflect.ValueOf(q).Kind() == reflect.Struct {

View File

@@ -12,7 +12,7 @@ import (
"strconv"
"strings"
"github.com/go-ldap/ldap"
"github.com/go-ldap/ldap/v3"
)
// Code relating to AD integration
@@ -236,7 +236,7 @@ func LdapGetGroupMembership(username string, password string) ([]string, error)
defer ldaps.Close()
// try an authenticated bind to AD to verify credentials
log.Printf("GetLdapGroupMembership Attempting LDAP bind with user '%s' and password length '%d'\n", username, len(password))
log.Printf("LdapGetGroupMembership Attempting LDAP bind with user '%s' and password length '%d'\n", username, len(password))
err = ldaps.Bind(username, password)
if err != nil {
if ldapErr, ok := err.(*ldap.Error); ok && ldapErr.ResultCode == ldap.LDAPResultInvalidCredentials {
@@ -244,17 +244,17 @@ func LdapGetGroupMembership(username string, password string) ([]string, error)
log.Print(errString)
return nil, errors.New(errString)
} else {
errString := fmt.Sprintf("GetLdapGroupMembership error binding to LDAP with supplied credentials : '%s'\n", err)
errString := fmt.Sprintf("LdapGetGroupMembership error binding to LDAP with supplied credentials : '%s'\n", err)
log.Print(errString)
return nil, errors.New(errString)
}
} else {
log.Printf("GetLdapGroupMembership successfully bound to LDAP\n")
log.Printf("LdapGetGroupMembership successfully bound to LDAP\n")
}
groups, err := GetGroupsOfUser(username, LdapBaseDn, ldaps)
if err != nil {
errString := fmt.Sprintf("GetLdapGroupMembership group search error : '%s'\n", err)
errString := fmt.Sprintf("LdapGetGroupMembership group search error : '%s'\n", err)
log.Print(errString)
return nil, errors.New(errString)
}
@@ -373,3 +373,12 @@ func GetLdapUserDn(username string, baseDN string, conn *ldap.Conn) (string, err
}
}
}
// Returns the user portion of a UPN formatted username
func GetUserFromUPN(email string) string {
parts := strings.Split(email, "@")
if len(parts) > 0 {
return parts[0]
}
return ""
}

View File

@@ -142,3 +142,29 @@ func (p *Permission) PermissionDelete() error {
return nil
}
// PermissionUpdate updates an existing permission definition in the database
func (p *Permission) PermissionUpdate() (*Permission, error) {
var err error
log.Printf("PermissionUpdate storing values '%v'\n", p)
if p.PermissionId == 0 {
err = errors.New("PermissionUpdate unable to update permission with empty PermissionId field")
log.Printf("PermissionUpdate error in pre-check : '%s'\n", err)
return p, err
}
result, err := db.NamedExec((`UPDATE permissions SET Description = :Description, ReadOnly = :ReadOnly, SafeId = :SafeId, UserId = :UserId, GroupId = :GroupId WHERE PermissionId = :PermissionId`), p)
if err != nil {
log.Printf("PermissionUpdate error executing sql record : '%s'\n", err)
return &Permission{}, err
} else {
affected, _ := result.RowsAffected()
id, _ := result.LastInsertId()
log.Printf("PermissionUpdate returned result id '%d' affecting %d row(s).\n", id, affected)
}
return p, nil
}

View File

@@ -11,35 +11,39 @@ import (
"log"
"smt/utils"
"strings"
"time"
)
const nonceSize = 12
// We use the json:"-" field tag to prevent showing these details to the user
type Secret struct {
SecretId int `db:"SecretId" json:"secretId"`
SafeId int `db:"SafeId" json:"safeId"`
DeviceName string `db:"DeviceName" json:"deviceName"`
DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"`
UserName string `db:"UserName" json:"userName"`
Secret string `db:"Secret" json:"secret"`
SecretId int `db:"SecretId" json:"secretId"`
SafeId int `db:"SafeId" json:"safeId"`
DeviceName string `db:"DeviceName" json:"deviceName"`
DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"`
UserName string `db:"UserName" json:"userName"`
Secret string `db:"Secret" json:"secret"`
LastUpdated time.Time `db:"LastUpdated" json:"lastUpdated"`
}
// SecretRestricted is for when we want to output a Secret but not the protected information
type SecretRestricted struct {
SecretId int `db:"SecretId" json:"secretId"`
SafeId int `db:"SafeId" json:"safeId"`
DeviceName string `db:"DeviceName" json:"deviceName"`
DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"`
UserName string `db:"UserName" json:"userName"`
Secret string `db:"Secret" json:"-"`
SecretId int `db:"SecretId" json:"secretId"`
SafeId int `db:"SafeId" json:"safeId"`
DeviceName string `db:"DeviceName" json:"deviceName"`
DeviceCategory string `db:"DeviceCategory" json:"deviceCategory"`
UserName string `db:"UserName" json:"userName"`
Secret string `db:"Secret" json:"-"`
LastUpdated time.Time `db:"LastUpdated" json:"lastUpdated"`
}
// Used for querying all secrets the user has access to
// Since there are some ambiguous column names (eg UserName is present in both users and secrets table), the order of fields in this struct matters
type UserSecret struct {
Secret
UserUserId int `db:"UserUserId"`
UserUserId int `db:"UserUserId"`
UserUserName string `db:"UserUserName"`
User
//Group
Permission
@@ -51,11 +55,15 @@ func (s Secret) GetId() int {
}
func (s *Secret) SaveSecret() (*Secret, error) {
var err error
// Populate timestamp field if not already set
if s.LastUpdated.IsZero() {
s.LastUpdated = time.Now().UTC()
}
log.Printf("SaveSecret storing values '%v'\n", s)
result, err := db.NamedExec((`INSERT INTO secrets (SafeId, DeviceName, DeviceCategory, UserName, Secret) VALUES (:SafeId, :DeviceName, :DeviceCategory, :UserName, :Secret)`), s)
result, err := db.NamedExec((`INSERT INTO secrets (SafeId, DeviceName, DeviceCategory, UserName, Secret, LastUpdated) VALUES (:SafeId, :DeviceName, :DeviceCategory, :UserName, :Secret, :LastUpdated)`), s)
if err != nil {
log.Printf("StoreSecret error executing sql record : '%s'\n", err)
@@ -78,7 +86,7 @@ func SecretsGetAllowed(s *Secret, userId int) ([]UserSecret, error) {
// Query for group access
queryArgs := []interface{}{}
query := `
SELECT users.UserId AS UserUserId, permissions.*,
SELECT users.UserId AS UserUserId, users.UserName AS UserUserName, permissions.*,
secrets.SecretId, secrets.SafeId, secrets.DeviceName, secrets.DeviceCategory, secrets.UserName
FROM users
INNER JOIN groups ON users.GroupId = groups.GroupId
@@ -88,6 +96,11 @@ func SecretsGetAllowed(s *Secret, userId int) ([]UserSecret, error) {
queryArgs = append(queryArgs, userId)
// Add any other arguments to the query if they were specified
if s.SecretId > 0 {
query += " AND SecretId = ? "
queryArgs = append(queryArgs, s.SecretId)
}
if s.DeviceName != "" {
query += " AND DeviceName LIKE ? "
queryArgs = append(queryArgs, s.DeviceName)
@@ -106,7 +119,7 @@ func SecretsGetAllowed(s *Secret, userId int) ([]UserSecret, error) {
// Query for user access
query += `
UNION
SELECT users.UserId AS UserUserId, permissions.*,
SELECT users.UserId AS UserUserId, users.UserName AS UserUserName, permissions.*,
secrets.SecretId, secrets.SafeId, secrets.DeviceName, secrets.DeviceCategory, secrets.UserName
FROM users
INNER JOIN permissions ON users.UserId = permissions.UserId
@@ -116,6 +129,10 @@ func SecretsGetAllowed(s *Secret, userId int) ([]UserSecret, error) {
queryArgs = append(queryArgs, userId)
// Add any other arguments to the query if they were specified
if s.SecretId > 0 {
query += " AND SecretId = ? "
queryArgs = append(queryArgs, s.SecretId)
}
if s.DeviceName != "" {
query += " AND DeviceName LIKE ? "
queryArgs = append(queryArgs, s.DeviceName)
@@ -132,37 +149,42 @@ func SecretsGetAllowed(s *Secret, userId int) ([]UserSecret, error) {
}
// Execute the query
log.Printf("SecretsGetAllowed query string : '%s'\nArguments:%+v\n", query, queryArgs)
//log.Printf("SecretsGetAllowed query string : '%s'\nArguments:%+v\n", query, queryArgs)
rows, err := db.Queryx(query, queryArgs...)
if err != nil {
log.Printf("SecretsGetAllowed error executing sql record : '%s'\n", err)
return secretResults, err
} else {
//log.Printf("SecretsGetAllowed any error '%s'\n", rows.Err())
// parse all the results into a slice
for rows.Next() {
//log.Printf("SecretsGetAllowed processing row\n")
var r UserSecret
err = rows.StructScan(&r)
//log.Printf("SecretsGetAllowed performed struct scan\n")
if err != nil {
log.Printf("SecretsGetAllowed error parsing sql record : '%s'\n", err)
return secretResults, err
}
//log.Printf("r: %v\n", r)
//log.Printf("SecretsGetAllowed performed err check\n")
// work around to get the UserId populated in the User field of the struct
r.User.UserId = r.UserUserId
r.User.UserName = r.UserUserName
// For debugging purposes
debugPrint := utils.PrintStructContents(&r, 0)
log.Println(debugPrint)
// Append the secrets to the query output, don't decrypt the secrets (we didn't SELECT them anyway)
//secretResults = append(secretResults, r)
//debugPrint := utils.PrintStructContents(&r, 0)
//log.Println(debugPrint)
// Use generics and the GetID() method on the UserSecret struct
// to avoid adding this element to the results
// if there is already a secret with the same ID present
secretResults = utils.AppendIfNotExists(secretResults, r)
//log.Printf("SecretsGetAllowed added secret results\n")
}
log.Printf("SecretsGetAllowed retrieved '%d' results\n", len(secretResults))
}
@@ -175,7 +197,7 @@ func SecretsGetFromMultipleSafes(s *Secret, safeIds []int) ([]Secret, error) {
var err error
var secretResults []Secret
args := []interface{}{}
queryArgs := []interface{}{}
var query string
// Generate placeholders for the IN clause to match multiple SafeId values
placeholders := make([]string, len(safeIds))
@@ -189,28 +211,33 @@ func SecretsGetFromMultipleSafes(s *Secret, safeIds []int) ([]Secret, error) {
// Add the Safe Ids to the arguments list
for _, g := range safeIds {
args = append(args, g)
queryArgs = append(queryArgs, g)
}
// Add any other arguments to the query if they were specified
if s.SecretId > 0 {
query += " AND SecretId = ? "
queryArgs = append(queryArgs, s.SecretId)
}
if s.DeviceName != "" {
query += " AND DeviceName LIKE ? "
args = append(args, s.DeviceName)
queryArgs = append(queryArgs, s.DeviceName)
}
if s.DeviceCategory != "" {
query += " AND DeviceCategory LIKE ? "
args = append(args, s.DeviceCategory)
queryArgs = append(queryArgs, s.DeviceCategory)
}
if s.UserName != "" {
query += " AND UserName LIKE ? "
args = append(args, s.UserName)
queryArgs = append(queryArgs, s.UserName)
}
// Execute the query
log.Printf("SecretsGetMultipleSafes query string :\n'%s'\nQuery Args : %+v\n", query, args)
rows, err := db.Queryx(query, args...)
//log.Printf("SecretsGetMultipleSafes query string :\n'%s'\nQuery Args : %+v\n", query, queryArgs)
rows, err := db.Queryx(query, queryArgs...)
if err != nil {
log.Printf("SecretsGetMultipleSafes error executing sql record : '%s'\n", err)
@@ -246,6 +273,11 @@ func (s *Secret) UpdateSecret() (*Secret, error) {
var err error
// Populate timestamp field if not already set
if s.LastUpdated.IsZero() {
s.LastUpdated = time.Now().UTC()
}
log.Printf("UpdateSecret storing values '%v'\n", s)
if s.SecretId == 0 {
@@ -254,7 +286,7 @@ func (s *Secret) UpdateSecret() (*Secret, error) {
return s, err
}
result, err := db.NamedExec((`UPDATE secrets SET DeviceName = :DeviceName, DeviceCategory = :DeviceCategory, UserName = :UserName, Secret = :Secret WHERE SecretId = :SecretId`), s)
result, err := db.NamedExec((`UPDATE secrets SET DeviceName = :DeviceName, DeviceCategory = :DeviceCategory, UserName = :UserName, Secret = :Secret, LastUpdated = :LastUpdated WHERE SecretId = :SecretId`), s)
if err != nil {
log.Printf("UpdateSecret error executing sql record : '%s'\n", err)
return &Secret{}, err

View File

@@ -5,20 +5,22 @@ import (
"errors"
"fmt"
"log"
"smt/utils"
"smt/utils/token"
"strings"
"time"
"golang.org/x/crypto/bcrypt"
)
type User struct {
UserId int `db:"UserId" json:"userId"`
GroupId int `db:"GroupId" json:"groupId"`
UserName string `db:"UserName" json:"userName"`
Password string `db:"Password" json:"-"`
LdapUser bool `db:"LdapUser" json:"ldapUser"`
Admin bool `db:"Admin"`
//LdapDn string `db:"LdapDn" json:"ldapDn"`
UserId int `db:"UserId" json:"userId"`
GroupId int `db:"GroupId" json:"groupId"`
UserName string `db:"UserName" json:"userName"`
Password string `db:"Password" json:"-"`
LdapUser bool `db:"LdapUser" json:"ldapUser"`
Admin bool `db:"Admin"`
LastLogin time.Time `db:"LastLogin" json:"lastLogin"`
LdapGroup bool `db:"LdapGroup"`
}
type UserRole struct {
@@ -49,12 +51,16 @@ func (u *User) SaveUser() (*User, error) {
var err error
if u.LastLogin.IsZero() {
u.LastLogin = time.Time{}
}
// Validate username not already in use
_, err = UserGetByName(u.UserName)
if err != nil && err.Error() == "user not found" {
log.Printf("SaveUser confirmed no existing user, continuing with creation of user '%s'\n", u.UserName)
//log.Printf("u: %v\n", u)
result, err := db.NamedExec((`INSERT INTO users (GroupId, UserName, Password, LdapUser, Admin) VALUES (:GroupId, :UserName, :Password, :LdapUser, :Admin)`), u)
result, err := db.NamedExec((`INSERT INTO users (GroupId, UserName, Password, LdapUser, Admin, LastLogin) VALUES (:GroupId, :UserName, :Password, :LdapUser, :Admin, :LastLogin)`), u)
if err != nil {
log.Printf("SaveUser error executing sql record : '%s'\n", err)
@@ -107,7 +113,7 @@ func VerifyPassword(password, hashedPassword string) error {
return errors.New("unable to compare password with empty hash")
}
log.Printf("VerifyPassword comparing input against hashed value '%s'\n", hashedPassword)
//log.Printf("VerifyPassword comparing input against hashed value '%s'\n", hashedPassword)
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}
@@ -118,12 +124,32 @@ func LoginCheck(username string, password string) (string, error) {
// Query database for matching user object
// Use IFNULL to handle situation where a user might not be a member of a group
err = db.QueryRowx("SELECT UserId, IFNULL(GroupId, 0) GroupId, UserName, Password, LdapUser, Admin FROM Users WHERE Username=?", username).StructScan(&u)
// Join on groups table so we can get the value in LdapGroup column
// if username is UPN format then get just the user portion
if strings.Contains(username, "@") {
plainUser := GetUserFromUPN(username)
// check for original username or plainUser
err = db.QueryRowx(`
SELECT users.UserId, IFNULL(users.GroupId, 0) GroupId, UserName, Password, LdapUser, users.Admin, groups.LdapGroup FROM Users
INNER JOIN groups ON users.GroupId = groups.GroupId
WHERE Username=? OR Username=?`, username, plainUser).StructScan(&u)
} else {
err = db.QueryRowx(`
SELECT users.UserId, IFNULL(users.GroupId, 0) GroupId, UserName, Password, LdapUser, users.Admin, groups.LdapGroup FROM Users
INNER JOIN groups ON users.GroupId = groups.GroupId
WHERE Username=?`, username).StructScan(&u)
}
if err != nil {
if err == sql.ErrNoRows {
log.Printf("LoginCheck found no users matching username '%s'\n", username)
// TODO - if username contains UPN style login then try extracting just the username and doing a query on that
// check LDAP if enabled
if LdapEnabled {
log.Printf("LoginCheck initiating ldap lookup for username '%s'\n", username)
ldapUser, err := UserLdapNewLoginCheck(username, password)
if err != nil {
errString := fmt.Sprintf("LoginCheck error checking LDAP for user : '%s'\n", err)
@@ -154,10 +180,11 @@ func LoginCheck(username string, password string) (string, error) {
return "", errors.New(errString)
}
} else {
log.Printf("LoginCheck retrieved user '%v' from database\n", u)
//log.Printf("LoginCheck retrieved user '%v' from database\n", u)
log.Printf("LoginCheck retrieved user id '%d' from database\n", u.UserId)
}
log.Printf("u: %v\n", u)
//log.Printf("u: %v\n", u)
if !u.LdapUser {
// Locally defined user, perform password verification
@@ -181,14 +208,22 @@ func LoginCheck(username string, password string) (string, error) {
} else {
log.Printf("LoginCheck successfully verified LDAP user\n")
// confirm that current LDAP group membership matches a group
err := UserLdapGroupVerify(username, password)
// check if user's group membership is an ldap group or not
log.Printf("User id '%d' is a member of group '%d' which has ldapGroup status '%v'\n", u.UserId, u.GroupId, u.LdapGroup)
if err != nil {
// No valid group membership
errString := fmt.Sprintf("ldap group membership check unsuccessful : '%s'\n", err)
log.Printf("LoginCheck %s\n", errString)
return "", errors.New(errString)
// If user's group membership is an ldap group, then run UserLdapGroupVerify as we were doing before
if u.LdapGroup {
// confirm that current LDAP group membership matches a group
err := UserLdapGroupVerify(username, password)
if err != nil {
// No valid group membership
errString := fmt.Sprintf("ldap group membership check unsuccessful : '%s'\n", err)
log.Printf("LoginCheck %s\n", errString)
return "", errors.New(errString)
}
} else { // If user's group membership is not an ldap group, then we are fine and the login attempt was successful
log.Printf("No need to check ldap group membership since user is not a member of an ldap group\n")
}
}
} else {
@@ -206,6 +241,8 @@ func LoginCheck(username string, password string) (string, error) {
return "", err
}
u.UserSetLastLogin()
return token, nil
}
@@ -260,6 +297,10 @@ func UserLdapNewLoginCheck(username string, password string) (User, error) {
matchFound := false
for _, group := range groupList {
// Skip any groups that aren't LDAP groups
if len(group.LdapDn) == 0 {
continue
}
for _, lg := range ldapGroups {
if group.LdapDn == lg {
log.Printf("Found match with groupname '%s' and LDAP group DN '%s', user is a member of group ID '%d'\n", group.GroupName, group.LdapDn, group.GroupId)
@@ -281,15 +322,23 @@ func UserLdapNewLoginCheck(username string, password string) (User, error) {
return u, nil
}
/*
// StoreLdapUser creates a user record in the database and returns the corresponding userId
func StoreLdapUser(u *User) error {
func (u *User) UserSetLastLogin() error {
// TODO
u.LastLogin = time.Now().UTC()
result, err := db.NamedExec((`UPDATE users SET LastLogin = :LastLogin WHERE UserId = :UserId`), u)
if err != nil {
log.Printf("UserSetLastLogin error executing sql update : '%s'\n", err)
return err
} else {
affected, _ := result.RowsAffected()
id, _ := result.LastInsertId()
log.Printf("UserSetLastLogin returned result id '%d' affecting %d row(s).\n", id, affected)
}
return nil
}
*/
func UserGetByID(uid uint) (User, error) {
@@ -389,7 +438,7 @@ func UserGetSafesAllowed(userId int) ([]UserSafe, error) {
// join users, groups and permissions
rows, err := db.Queryx(`
SELECT users.UserId, users.GroupId,
SELECT users.UserId, users.GroupId, users.UserName,
permissions.SafeId, permissions.ReadOnly, safes.SafeName FROM users
INNER JOIN groups ON users.GroupId = groups.GroupId
INNER JOIN permissions ON groups.GroupId = permissions.GroupId
@@ -421,12 +470,13 @@ func UserGetSafesAllowed(userId int) ([]UserSafe, error) {
return results, err
}
//log.Printf("UserGetSafesAllowed adding record : '%+v'\n", us)
debugPrint := utils.PrintStructContents(&us, 0)
log.Printf("UserGetSafesAllowed adding record :\n%s\n", debugPrint)
//debugPrint := utils.PrintStructContents(&us, 0)
//log.Printf("UserGetSafesAllowed adding record :\n%s\n", debugPrint)
results = append(results, us)
/*
// For intense debugging
// Create a map to store column names and values
rowValues := make(map[string]interface{})
@@ -445,7 +495,7 @@ func UserGetSafesAllowed(userId int) ([]UserSafe, error) {
log.Println("-----------")
*/
}
log.Printf("UserGetSafesAllowed retrieved '%d' results\n", len(results))
//log.Printf("UserGetSafesAllowed retrieved '%d' results\n", len(results))
}
return results, nil
@@ -458,7 +508,7 @@ func UserList() ([]User, error) {
rows, err := db.Queryx("SELECT * FROM users")
if err != nil {
log.Printf("QueryUsers error executing sql record : '%s'\n", err)
log.Printf("UserList error executing sql record : '%s'\n", err)
return results, err
} else {
// parse all the results into a slice
@@ -466,13 +516,13 @@ func UserList() ([]User, error) {
var u User
err = rows.StructScan(&u)
if err != nil {
log.Printf("QueryUsers error parsing sql record : '%s'\n", err)
log.Printf("UserList error parsing sql record : '%s'\n", err)
return results, err
}
results = append(results, u)
}
log.Printf("QueryUsers retrieved '%d' results\n", len(results))
log.Printf("UserList retrieved '%d' results\n", len(results))
}
return results, nil

View File

@@ -4,8 +4,52 @@ import (
"fmt"
"reflect"
"strings"
"time"
)
/*
func PrintStructContents(s interface{}, indentLevel int) string {
var result strings.Builder
val := reflect.ValueOf(s)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldType := typ.Field(i)
log.Printf("PrintStructContents [%d] field '%s' (%T)\n", i, field, fieldType)
indent := strings.Repeat("\t", indentLevel)
result.WriteString(fmt.Sprintf("%s%s: ", indent, fieldType.Name))
log.Printf("%s%s: \n", indent, fieldType.Name)
switch field.Kind() {
case reflect.Struct:
result.WriteString("\n")
foo := PrintStructContents(field.Interface(), indentLevel+1)
log.Printf("foo: %s\n", foo)
result.WriteString(foo)
case reflect.Uint64:
log.Printf("uint64 %v\n", field.Interface())
result.WriteString(fmt.Sprintf("%v\n", field.Interface()))
default:
log.Printf("default %v\n", field.Interface())
result.WriteString(fmt.Sprintf("%v\n", field.Interface()))
}
}
log.Printf("PrintStructContents completed\n")
return result.String()
}
*/
func PrintStructContents(s interface{}, indentLevel int) string {
var result strings.Builder
@@ -26,8 +70,13 @@ func PrintStructContents(s interface{}, indentLevel int) string {
switch field.Kind() {
case reflect.Struct:
result.WriteString("\n")
result.WriteString(PrintStructContents(field.Interface(), indentLevel+1))
if fieldType.Type == reflect.TypeOf(time.Time{}) {
// Handle time.Time field
result.WriteString(fmt.Sprintf("%v\n", field.Interface().(time.Time).Format("2006-12-24 15:04:05")))
} else {
result.WriteString("\n")
result.WriteString(PrintStructContents(field.Interface(), indentLevel+1))
}
default:
result.WriteString(fmt.Sprintf("%v\n", field.Interface()))
}
@@ -53,3 +102,20 @@ func AppendIfNotExists[T Identifiable](slice []T, element T) []T {
// Element with the same Id does not exist, append the new element
return append(slice, element)
}
// UpdateStruct updates the values in the destination struct with values from the source struct
func UpdateStruct(dest interface{}, src interface{}) {
destValue := reflect.ValueOf(dest).Elem()
srcValue := reflect.ValueOf(src).Elem()
destType := destValue.Type()
for i := 0; i < srcValue.NumField(); i++ {
srcField := srcValue.Field(i)
destField := destValue.FieldByName(destType.Field(i).Name)
if destField.IsValid() && destField.Type() == srcField.Type() && destField.CanSet() {
destField.Set(srcField)
}
}
}

View File

@@ -8,8 +8,9 @@ import (
"strings"
"time"
jwt "github.com/dgrijalva/jwt-go"
//jwt "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
jwt "github.com/golang-jwt/jwt/v5"
)
func GenerateToken(user_id uint) (string, error) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 86 KiB