Compare commits
63 Commits
Author | SHA1 | Date | |
---|---|---|---|
2754fb8144 | |||
4650a971a3 | |||
8cd1292a41 | |||
ea5198a5b9 | |||
d000469836 | |||
02061f5b26 | |||
ea3e8ddfbc | |||
8182b899cf | |||
ae5b864feb | |||
4a6a7270f9 | |||
a7beb94341 | |||
4a0e98bab8 | |||
66a1917e6f | |||
526161f6b4 | |||
ff16acc816 | |||
ee822b5c9d | |||
e427184310 | |||
5719ce8f5d | |||
a78f2b7c88 | |||
1171a7bbaa | |||
8ff92e206e | |||
19ffc9e683 | |||
968bcf1b7a | |||
77d063867a | |||
c82bffe421 | |||
b801563074 | |||
4bc430633e | |||
69a25fbb09 | |||
840b9f4863 | |||
5b87ef0d30 | |||
bfc734a6d1 | |||
b5c9b5ce19 | |||
9f0dafd4fd | |||
abaa291a14 | |||
de1a076d64 | |||
1b5a2e89dd | |||
8799f0f796 | |||
317e0ab83d | |||
116a9e827b | |||
2ab6240a24 | |||
bb3bf3093d | |||
1d1aa098a9 | |||
f68bd9637d | |||
5f63ee235b | |||
092fe32baf | |||
44d3bc71ed | |||
dc3c5d1068 | |||
d834a5c362 | |||
9e0c1e7cd7 | |||
9ac729b684 | |||
7f43662cbc | |||
b35d365467 | |||
77d487c1ce | |||
f5827ef432 | |||
6e1a28d2df | |||
5c3b2e19cf | |||
e109cd084d | |||
f29c733080 | |||
97cd75b0d7 | |||
b278a3c7d8 | |||
498dd9a8c3 | |||
c99ffa8368 | |||
8fec84c118 |
40
.drone.yml
40
.drone.yml
@@ -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
117
README.md
@@ -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
|
||||

|
@@ -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})
|
||||
|
@@ -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)
|
||||
|
@@ -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(¤tPermission, &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})
|
||||
}
|
||||
|
23
controllers/retrieveAudits.go
Normal file
23
controllers/retrieveAudits.go
Normal 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})
|
||||
}
|
@@ -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})
|
||||
*/
|
||||
}
|
||||
|
@@ -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
77
go.mod
@@ -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
265
go.sum
@@ -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
18
main.go
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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 {
|
@@ -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 ""
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
114
models/user.go
114
models/user.go
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
BIN
www/database.png
BIN
www/database.png
Binary file not shown.
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 86 KiB |
Reference in New Issue
Block a user