Skip to content

Commit

Permalink
Add #302 Add endpoint for admin logs
Browse files Browse the repository at this point in the history
albinpa authored and georgepadayatti committed Oct 21, 2023
1 parent 8a6373f commit 7dfd0ba
Showing 8 changed files with 267 additions and 5 deletions.
89 changes: 89 additions & 0 deletions src/v2/actionlog/actionlog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package actionlog

import (
"context"

"github.com/bb-consent/api/src/database"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)

// Log type const
const (
LogTypeSecurity = 1
LogTypeAPICalls = 2
LogTypeOrgUpdates = 3
LogTypeUserUpdates = 4
LogTypeWebhook = 5
)

// LogType Log type
type LogType struct {
ID int
Str string
}

// Note: Dont change the ID(s) if new type is needed then add at the end

// LogTypes Array of id and string
var LogTypes = []LogType{
{ID: LogTypeSecurity, Str: "Security"},
{ID: LogTypeAPICalls, Str: "API calls"},
{ID: LogTypeOrgUpdates, Str: "OrgUpdates"},
{ID: LogTypeUserUpdates, Str: "UserUpdates"},
{ID: LogTypeWebhook, Str: "Webhooks"}}

// GetTypeStr Get type string from ID
func GetTypeStr(logType int) string {
return LogTypes[logType-1].Str
}

// ActionLog All access logs
type ActionLog struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Type int `json:"type"`
TypeStr string `json:"typeStr"`
OrgID string `json:"orgId"`
UserID string `json:"userId"`
UserName string `json:"userName"`
Action string `json:"action"` //Free string storing the real log
}

type ActionLogRepository struct {
DefaultFilter bson.M
}

// Init
func (actionLogRepo *ActionLogRepository) Init(organisationId string) {
actionLogRepo.DefaultFilter = bson.M{"orgid": organisationId}
}

func Collection() *mongo.Collection {
return database.DB.Client.Database(database.DB.Name).Collection("actionLogs")
}

// Add Adds access log
func Add(log ActionLog) error {
_, err := Collection().InsertOne(context.TODO(), log)
if err != nil {
return err
}
return nil
}

// GetAccessLogByOrgID gets all notifications of a given user
func (actionLogRepo *ActionLogRepository) GetAccessLogByOrgID() (results []ActionLog, err error) {
filter := actionLogRepo.DefaultFilter

cursor, err := Collection().Find(context.TODO(), filter)
if err != nil {
return nil, err
}
defer cursor.Close(context.TODO())

if err := cursor.All(context.TODO(), &results); err != nil {
return nil, err
}
return results, err
}
54 changes: 54 additions & 0 deletions src/v2/actionlog/actionlog_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package actionlog

import (
"fmt"
"log"
)

func doActionLog(l ActionLog) error {
err := Add(l)
if err != nil {
log.Printf("Failed to add access log for user: %v org: %v", l.UserID, l.OrgID)
}
fmt.Printf("%v \n", l)
return err
}

// LogOrgSecurityCalls Logs all important access related entries
func LogOrgSecurityCalls(userID string, uName string, orgID string, aLog string) {
var l ActionLog
l.OrgID = orgID
l.UserID = userID
l.UserName = uName
l.Action = aLog
l.Type = LogTypeSecurity
l.TypeStr = GetTypeStr(l.Type)

doActionLog(l)
}

// LogOrgWebhookCalls Logs all webhook triggers based on different events
func LogOrgWebhookCalls(userID string, uName string, orgID string, aLog string) {
var l ActionLog
l.OrgID = orgID
l.UserID = userID
l.UserName = uName
l.Action = aLog
l.Type = LogTypeWebhook
l.TypeStr = GetTypeStr(l.Type)

doActionLog(l)
}

// LogOrgAPICalls Organization API calls are logged for future reference
func LogOrgAPICalls(userID string, uName string, orgID string, aLog string) {
var l ActionLog
l.OrgID = orgID
l.UserID = userID
l.UserName = uName
l.Action = aLog
l.Type = LogTypeAPICalls
l.TypeStr = GetTypeStr(l.Type)

doActionLog(l)
}
118 changes: 118 additions & 0 deletions src/v2/handler/audit/audit_admin_logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package audit

import (
"context"
"encoding/json"
"errors"
"log"
"net/http"
"strconv"

"github.com/bb-consent/api/src/common"
"github.com/bb-consent/api/src/config"
"github.com/bb-consent/api/src/v2/actionlog"
"github.com/bb-consent/api/src/v2/paginate"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)

// ListActionLogsError is an error enumeration for action logs API.
type ListActionLogsError int

const (
// ActionLogTypeIsMissingError indicates that the action log query param is missing.
ActionLogTypeIsMissingError ListActionLogsError = iota
)

// Error returns the string representation of the error.
func (e ListActionLogsError) Error() string {
switch e {
case ActionLogTypeIsMissingError:
return "Query param action log type is missing!"
default:
return "Unknown error!"
}
}

// ParseListActionLogQueryParams parses query params for listing action logs.
func ParseListActionLogQueryParams(r *http.Request) (int, error) {
query := r.URL.Query()
var logType int

// Check if logType query param is provided.
if r, ok := query["logType"]; ok && len(r) > 0 {
if oInt, err := strconv.Atoi(r[0]); err == nil && oInt >= 1 {
logType = oInt
return logType, nil
}
}

return logType, ActionLogTypeIsMissingError
}

type listActionLogsResp struct {
ActionLogs interface{} `json:"logs"`
Pagination paginate.Pagination `json:"pagination"`
}

func returnHTTPResponse(resp interface{}, w http.ResponseWriter) {
response, _ := json.Marshal(resp)
w.Header().Set(config.ContentTypeHeader, config.ContentTypeJSON)
w.WriteHeader(http.StatusOK)
w.Write(response)
}

// AuditGetOrgLogs Get action logs for the organization
func AuditGetOrgLogs(w http.ResponseWriter, r *http.Request) {
organisationId := r.Header.Get(config.OrganizationId)
organisationId = common.Sanitize(organisationId)

// Query params
offset, limit := paginate.ParsePaginationQueryParams(r)
log.Printf("Offset: %v and limit: %v\n", offset, limit)

// Repository
actionLogRepo := actionlog.ActionLogRepository{}
actionLogRepo.Init(organisationId)

var filter primitive.M

logType, err := ParseListActionLogQueryParams(r)
if err != nil && errors.Is(err, ActionLogTypeIsMissingError) {
filter = actionLogRepo.DefaultFilter
} else {
filter = common.CombineFilters(actionLogRepo.DefaultFilter, bson.M{"type": logType})
}

// Return all action logs
var actionLogs []actionlog.ActionLog
query := paginate.PaginateDBObjectsQuery{
Filter: filter,
Collection: actionlog.Collection(),
Context: context.Background(),
Limit: limit,
Offset: offset,
}
result, err := paginate.PaginateDBObjects(query, &actionLogs)
if err != nil {
if errors.Is(err, paginate.EmptyDBError) {
emptyActionLogs := make([]interface{}, 0)
resp := listActionLogsResp{
ActionLogs: emptyActionLogs,
Pagination: result.Pagination,
}
returnHTTPResponse(resp, w)
return
}
m := "Failed to paginate action log"
common.HandleErrorV2(w, http.StatusInternalServerError, m, err)
return

}
resp := listActionLogsResp{
ActionLogs: result.Items,
Pagination: result.Pagination,
}
returnHTTPResponse(resp, w)

}
2 changes: 1 addition & 1 deletion src/v2/handler/onboard/loginadminuser_handler.go
Original file line number Diff line number Diff line change
@@ -8,10 +8,10 @@ import (
"net/http"

"github.com/asaskevich/govalidator"
"github.com/bb-consent/api/src/actionlog"
"github.com/bb-consent/api/src/common"
"github.com/bb-consent/api/src/config"
"github.com/bb-consent/api/src/user"
"github.com/bb-consent/api/src/v2/actionlog"
"github.com/bb-consent/api/src/v2/iam"
"github.com/bb-consent/api/src/v2/token"
)
Original file line number Diff line number Diff line change
@@ -5,10 +5,10 @@ import (
"fmt"
"net/http"

"github.com/bb-consent/api/src/actionlog"
"github.com/bb-consent/api/src/common"
"github.com/bb-consent/api/src/config"
"github.com/bb-consent/api/src/user"
"github.com/bb-consent/api/src/v2/actionlog"
wh "github.com/bb-consent/api/src/v2/webhook"
"github.com/gorilla/mux"
)
2 changes: 1 addition & 1 deletion src/v2/http_path/audit_paths.go
Original file line number Diff line number Diff line change
@@ -6,4 +6,4 @@ const AuditAgreementList = "/v2/audit/agreements/"
const AuditReadRecord = "/v2/audit/agreement/{agreementId}/"

// organization action logs
const GetOrgLogs = "/v2/audit/admin/logs"
const AuditGetOrgLogs = "/v2/audit/admin/logs"
3 changes: 2 additions & 1 deletion src/v2/http_path/routes.go
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ package http_path
import (
v2Handler "github.com/bb-consent/api/src/v2/handler"
apiKeyHandler "github.com/bb-consent/api/src/v2/handler/apikey"
auditHandler "github.com/bb-consent/api/src/v2/handler/audit"
dataAgreementHandler "github.com/bb-consent/api/src/v2/handler/dataagreement"
dataAttributeHandler "github.com/bb-consent/api/src/v2/handler/dataattribute"
idpHandler "github.com/bb-consent/api/src/v2/handler/idp"
@@ -105,7 +106,7 @@ func SetRoutes(r *mux.Router, e *casbin.Enforcer) {
r.Handle(AuditReadRecord, m.Chain(v2Handler.AuditReadRecord, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET")

// organization action logs
r.Handle(GetOrgLogs, m.Chain(v2Handler.GetOrgLogs, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET")
r.Handle(AuditGetOrgLogs, m.Chain(auditHandler.AuditGetOrgLogs, m.Logger(), m.Authorize(e), m.SetApplicationMode(), m.Authenticate(), m.AddContentType())).Methods("GET")

// Onboard api(s)

2 changes: 1 addition & 1 deletion src/v2/webhook/webhook.go
Original file line number Diff line number Diff line change
@@ -9,10 +9,10 @@ import (
"strconv"
"time"

"github.com/bb-consent/api/src/actionlog"
"github.com/bb-consent/api/src/config"
"github.com/bb-consent/api/src/kafkaUtils"
"github.com/bb-consent/api/src/user"
"github.com/bb-consent/api/src/v2/actionlog"
"github.com/bb-consent/api/src/v2/webhook_dispatcher"
"github.com/confluentinc/confluent-kafka-go/kafka"
)

0 comments on commit 7dfd0ba

Please sign in to comment.