Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
bobgroup-go-utils
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Bob Public Utils
bobgroup-go-utils
Commits
a457f019
Commit
a457f019
authored
Jun 25, 2024
by
Jano Hendriks
Browse files
Options
Downloads
Patches
Plain Diff
Update audit events to handle custom formatting
parent
d82e4aa5
No related branches found
No related tags found
No related merge requests found
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
audit/audit.go
+189
-25
189 additions, 25 deletions
audit/audit.go
with
189 additions
and
25 deletions
audit/audit.go
+
189
−
25
View file @
a457f019
...
...
@@ -3,25 +3,29 @@ package audit
import
(
"encoding/json"
"fmt"
"github.com/r3labs/diff/v2"
"github.com/samber/lo"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/number_utils"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/reflection"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/r3labs/diff/v2"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/reflection"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils"
)
type
FieldChange
struct
{
From
interface
{}
`json:"change_from"`
To
interface
{}
`json:"change_to"`
From
any
`json:"change_from"`
To
any
`json:"change_to"`
}
func
VerifyAuditEvents
(
original
interface
{},
new
interface
{})
error
{
type
IAuditFormatter
interface
{
FormatForAuditEvent
()
string
}
func
VerifyAuditEvents
(
original
any
,
new
any
)
error
{
if
original
!=
nil
{
structValue
:=
reflect
.
ValueOf
(
original
)
if
structValue
.
Kind
()
!=
reflect
.
Struct
{
...
...
@@ -39,12 +43,12 @@ func VerifyAuditEvents(original interface{}, new interface{}) error {
return
nil
}
func
GetChanges
(
original
interface
{},
new
interface
{}
)
(
map
[
string
]
interface
{}
,
error
)
{
func
GetChanges
(
original
any
,
new
any
)
(
map
[
string
]
any
,
error
)
{
// Clean audit events
original
=
cleanStruct
(
original
)
new
=
cleanStruct
(
new
)
changes
:=
map
[
string
]
interface
{}
{}
changes
:=
map
[
string
]
any
{}
changelog
,
err
:=
diff
.
Diff
(
original
,
new
)
if
err
!=
nil
{
return
changes
,
err
...
...
@@ -64,6 +68,11 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
// ["Account", "ID"]
// 0 = Object
// 1 = field
objectKey
:=
ToSnakeCase
(
change
.
Path
[
0
])
didInsert
:=
CheckToFormatForAuditEvent
(
changes
,
original
,
new
,
change
.
Path
[
0
],
objectKey
,
-
1
)
if
didInsert
{
continue
}
ChildObjectChanges
(
changes
,
change
.
Path
[
0
],
change
.
Path
[
1
],
change
.
From
,
change
.
To
)
...
...
@@ -79,6 +88,11 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
if
!
string_utils
.
IsNumericString
(
indexString
)
{
// Not an array, but a deeper nested object
didInsert
:=
CheckToFormatForAuditEvent
(
changes
,
original
,
new
,
change
.
Path
[
0
],
objectKey
,
-
1
)
if
didInsert
{
continue
}
ChildObjectChanges
(
changes
,
change
.
Path
[
len
(
change
.
Path
)
-
2
],
change
.
Path
[
len
(
change
.
Path
)
-
1
],
change
.
From
,
change
.
To
)
continue
}
...
...
@@ -86,6 +100,11 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
index
,
_
:=
number_utils
.
StringToInt64
(
indexString
)
field
:=
ToSnakeCase
(
change
.
Path
[
2
])
didInsert
:=
CheckToFormatForAuditEvent
(
changes
,
original
,
new
,
change
.
Path
[
0
],
objectKey
,
int
(
index
))
if
didInsert
{
continue
}
if
len
(
change
.
Path
)
==
5
&&
string_utils
.
IsNumericString
(
change
.
Path
[
3
])
{
// The field is actually an array of objects.
field
+=
fmt
.
Sprintf
(
"[%s] (%s)"
,
change
.
Path
[
3
],
ToSnakeCase
(
change
.
Path
[
4
]))
...
...
@@ -93,7 +112,7 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
arrayObject
,
present
:=
changes
[
objectKey
]
if
present
{
if
arrayOfObjects
,
ok
:=
arrayObject
.
([]
map
[
string
]
interface
{}
);
ok
{
if
arrayOfObjects
,
ok
:=
arrayObject
.
([]
map
[
string
]
any
);
ok
{
arrayIndex
:=
ArrayIndexForObjectIndex
(
arrayOfObjects
,
index
)
if
arrayIndex
!=
-
1
{
// Add field to existing object in array
...
...
@@ -104,7 +123,7 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
}
}
else
{
// new object, append to existing array
fieldChange
:=
map
[
string
]
interface
{}
{
fieldChange
:=
map
[
string
]
any
{
"index"
:
index
,
field
:
FieldChange
{
From
:
change
.
From
,
...
...
@@ -117,14 +136,14 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
}
}
else
{
// Create array of objects
fieldChange
:=
map
[
string
]
interface
{}
{
fieldChange
:=
map
[
string
]
any
{
"index"
:
index
,
field
:
FieldChange
{
From
:
change
.
From
,
To
:
change
.
To
,
},
}
changes
[
objectKey
]
=
[]
map
[
string
]
interface
{}
{
changes
[
objectKey
]
=
[]
map
[
string
]
any
{
fieldChange
,
}
}
...
...
@@ -134,7 +153,7 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
return
changes
,
nil
}
func
cleanStruct
(
object
interface
{})
interface
{}
{
func
cleanStruct
(
object
any
)
any
{
defer
func
()
{
if
err
:=
recover
();
err
!=
nil
{
logs
.
ErrorMsg
(
fmt
.
Sprintf
(
"audit event panic: %+v"
,
err
))
...
...
@@ -175,9 +194,10 @@ func cleanStruct(object interface{}) interface{} {
structField
:=
val
.
Type
()
.
Field
(
i
)
// Determine whether the field should be included or excluded
value
,
_
:=
structField
.
Tag
.
Lookup
(
"audit"
)
shouldIncludeForAudit
:=
value
==
"true"
shouldExcludeForAudit
:=
value
==
"false"
auditTag
:=
structField
.
Tag
.
Get
(
"audit"
)
auditTagOptions
:=
strings
.
Split
(
auditTag
,
","
)
shouldIncludeForAudit
:=
lo
.
Contains
(
auditTagOptions
,
"true"
)
shouldExcludeForAudit
:=
lo
.
Contains
(
auditTagOptions
,
"false"
)
// If the audit tag is present and specified to 'true', we should always include the relation
if
shouldIncludeForAudit
{
...
...
@@ -197,14 +217,158 @@ func cleanStruct(object interface{}) interface{} {
return
object
}
func
ChildObjectChanges
(
changes
map
[
string
]
interface
{},
objectPath
string
,
fieldPath
string
,
changeFrom
interface
{},
changeTo
interface
{})
{
func
CheckToFormatForAuditEvent
(
changes
map
[
string
]
any
,
original
any
,
new
any
,
field
string
,
objectKey
string
,
index
int
)
(
didInsert
bool
)
{
originalStructField
,
originalFieldValue
,
found
:=
getFieldFromStruct
(
original
,
field
)
if
!
found
{
return
false
}
_
,
newFieldValue
,
found
:=
getFieldFromStruct
(
new
,
field
)
if
!
found
{
return
false
}
doGroupSlice
:=
doGroupSliceForAuditEvent
(
originalStructField
)
originalFormattedObject
:=
checkToFormatForAuditEvent
(
originalFieldValue
,
index
)
newFormattedObject
:=
checkToFormatForAuditEvent
(
newFieldValue
,
index
)
if
originalFormattedObject
!=
nil
||
newFormattedObject
!=
nil
{
if
_
,
present
:=
changes
[
objectKey
];
present
{
if
doGroupSlice
{
// The object has already been added to the changes - group the new change with the previous
existingChanges
,
ok
:=
changes
[
objectKey
]
.
(
FieldChange
)
if
!
ok
{
return
true
}
if
originalFormattedObject
!=
nil
{
existingChanges
.
From
=
appendNewChangeToExistingChanges
(
existingChanges
.
From
,
*
originalFormattedObject
)
}
if
newFormattedObject
!=
nil
{
existingChanges
.
To
=
appendNewChangeToExistingChanges
(
existingChanges
.
To
,
*
newFormattedObject
)
}
changes
[
objectKey
]
=
existingChanges
}
return
true
}
if
doGroupSlice
{
changes
[
objectKey
]
=
FieldChange
{
From
:
[]
string
{
*
originalFormattedObject
},
To
:
[]
string
{
*
newFormattedObject
},
}
}
else
{
if
index
>
-
1
{
objectKey
=
fmt
.
Sprintf
(
"%s[%d]"
,
field
,
index
)
}
changes
[
objectKey
]
=
FieldChange
{
From
:
originalFormattedObject
,
To
:
newFormattedObject
,
}
}
return
true
}
return
false
}
func
appendNewChangeToExistingChanges
(
existingChanges
any
,
newChange
string
)
any
{
existingChangesStrings
,
ok
:=
existingChanges
.
([]
string
)
if
!
ok
{
return
existingChanges
}
if
!
lo
.
Contains
(
existingChangesStrings
,
newChange
)
{
existingChangesStrings
=
append
(
existingChangesStrings
,
newChange
)
}
return
existingChangesStrings
}
func
getFieldFromStruct
(
object
any
,
field
string
)
(
reflect
.
StructField
,
reflect
.
Value
,
bool
)
{
objectValue
:=
reflect
.
ValueOf
(
object
)
if
objectValue
.
Kind
()
==
reflect
.
Ptr
{
objectValue
=
objectValue
.
Elem
()
}
if
objectValue
.
Kind
()
!=
reflect
.
Struct
{
// Fields can only be retrieved from structs
return
reflect
.
StructField
{},
reflect
.
Value
{},
false
}
objectStructField
,
found
:=
objectValue
.
Type
()
.
FieldByName
(
field
)
if
!
found
{
return
reflect
.
StructField
{},
reflect
.
Value
{},
false
}
objectFieldValue
:=
objectValue
.
FieldByName
(
field
)
return
objectStructField
,
objectFieldValue
,
true
}
func
doGroupSliceForAuditEvent
(
objectField
reflect
.
StructField
)
bool
{
auditTag
,
found
:=
objectField
.
Tag
.
Lookup
(
"audit"
)
if
found
{
auditTagOptions
:=
strings
.
Split
(
auditTag
,
","
)
if
lo
.
Contains
(
auditTagOptions
,
"group"
)
{
return
true
}
}
return
false
}
func
checkToExecuteAuditFormatter
(
fieldValue
reflect
.
Value
)
*
string
{
if
auditFormatterObject
,
ok
:=
fieldValue
.
Interface
()
.
(
IAuditFormatter
);
ok
{
fieldString
:=
auditFormatterObject
.
FormatForAuditEvent
()
if
fieldString
!=
""
{
return
&
fieldString
}
}
return
nil
}
func
checkToFormatForAuditEvent
(
fieldValue
reflect
.
Value
,
index
int
)
*
string
{
if
fieldValue
.
Kind
()
==
reflect
.
Ptr
{
fieldValue
=
fieldValue
.
Elem
()
}
var
formattedField
*
string
if
fieldValue
.
Kind
()
==
reflect
.
Struct
{
formattedField
=
checkToExecuteAuditFormatter
(
fieldValue
)
}
else
if
fieldValue
.
Kind
()
==
reflect
.
Slice
&&
fieldValue
.
Len
()
>
0
{
if
index
>=
fieldValue
.
Len
()
{
return
nil
}
sliceFieldValue
:=
fieldValue
.
Index
(
index
)
if
sliceFieldValue
.
Kind
()
==
reflect
.
Ptr
{
sliceFieldValue
=
sliceFieldValue
.
Elem
()
}
if
sliceFieldValue
.
Kind
()
!=
reflect
.
Struct
{
// Not a slice of structs - ignore the format flag
return
nil
}
formattedField
=
checkToExecuteAuditFormatter
(
sliceFieldValue
)
}
return
formattedField
}
func
ChildObjectChanges
(
changes
map
[
string
]
any
,
objectPath
string
,
fieldPath
string
,
changeFrom
any
,
changeTo
any
)
{
objectKey
:=
ToSnakeCase
(
objectPath
)
field
:=
ToSnakeCase
(
fieldPath
)
existingObject
,
present
:=
changes
[
objectKey
]
if
present
{
if
object
,
ok
:=
existingObject
.
(
map
[
string
]
interface
{}
);
ok
{
if
object
,
ok
:=
existingObject
.
(
map
[
string
]
any
);
ok
{
object
[
field
]
=
FieldChange
{
From
:
changeFrom
,
To
:
changeTo
,
...
...
@@ -212,7 +376,7 @@ func ChildObjectChanges(changes map[string]interface{}, objectPath string, field
changes
[
objectKey
]
=
object
}
}
else
{
fieldChange
:=
map
[
string
]
interface
{}
{
fieldChange
:=
map
[
string
]
any
{
field
:
FieldChange
{
From
:
changeFrom
,
To
:
changeTo
,
...
...
@@ -223,7 +387,7 @@ func ChildObjectChanges(changes map[string]interface{}, objectPath string, field
}
// ArrayIndexForObjectIndex gets the index of arrayOfObjects where the object's index field is equal to objectIndex.
func
ArrayIndexForObjectIndex
(
arrayOfObjects
[]
map
[
string
]
interface
{}
,
objectIndex
int64
)
int
{
func
ArrayIndexForObjectIndex
(
arrayOfObjects
[]
map
[
string
]
any
,
objectIndex
int64
)
int
{
for
arrayIndex
,
object
:=
range
arrayOfObjects
{
index
,
present
:=
object
[
"index"
]
if
present
{
...
...
@@ -238,8 +402,8 @@ func ArrayIndexForObjectIndex(arrayOfObjects []map[string]interface{}, objectInd
// GetAllChanges Returns the diff, structured in json, recursively
// Be warned, here be dragons. Debug this first to understand how it works
func
GetAllChanges
(
original
interface
{},
new
interface
{}
)
(
map
[
string
]
interface
{}
,
error
)
{
changes
:=
map
[
string
]
interface
{}
{}
func
GetAllChanges
(
original
any
,
new
any
)
(
map
[
string
]
any
,
error
)
{
changes
:=
map
[
string
]
any
{}
changelog
,
err
:=
diff
.
Diff
(
original
,
new
)
if
err
!=
nil
{
return
changes
,
err
...
...
@@ -402,7 +566,7 @@ func ToSnakeCase(str string) string {
return
strings
.
ToLower
(
snake
)
}
func
GetIntValue
(
object
interface
{}
,
key
string
)
int64
{
func
GetIntValue
(
object
any
,
key
string
)
int64
{
structValue
:=
reflect
.
ValueOf
(
object
)
if
structValue
.
Kind
()
==
reflect
.
Struct
{
field
:=
structValue
.
FieldByName
(
key
)
...
...
@@ -412,7 +576,7 @@ func GetIntValue(object interface{}, key string) int64 {
return
0
}
func
GetStringValue
(
object
interface
{}
,
key
string
)
string
{
func
GetStringValue
(
object
any
,
key
string
)
string
{
structValue
:=
reflect
.
ValueOf
(
object
)
if
structValue
.
Kind
()
==
reflect
.
Struct
{
field
:=
structValue
.
FieldByName
(
key
)
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
sign in
to comment