diff --git a/management/server/account.go b/management/server/account.go index cdb46fccb..3049dcafe 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -511,7 +511,18 @@ func (am *DefaultAccountManager) newAccount(userID, domain string) (*Account, er log.Warnf("an account with ID already exists, retrying...") continue } else if statusErr.Type() == status.NotFound { - return newAccountWithId(accountId, userID, domain), nil + newAccount := newAccountWithId(accountId, userID, domain) + _, err = am.eventStore.Save(&activity.Event{ + Timestamp: time.Now(), + Activity: activity.AccountCreated, + AccountID: newAccount.Id, + TargetID: newAccount.Id, + InitiatorID: userID, + }) + if err != nil { + return nil, err + } + return newAccount, nil } else { return nil, err } @@ -798,16 +809,15 @@ func (am *DefaultAccountManager) handleNewUserAccount(domainAcc *Account, claims return nil, err } - opEvent := &activity.Event{ - Timestamp: time.Now(), - Type: activity.ManagementEvent, - OperationCode: activity.UserJoinedOperation, - AccountID: account.Id, - TargetID: claims.UserId, - ModifierID: claims.UserId, + event := &activity.Event{ + Timestamp: time.Now(), + Activity: activity.UserJoined, + AccountID: account.Id, + TargetID: claims.UserId, + InitiatorID: claims.UserId, } - _, err = am.eventStore.Save(opEvent) + _, err = am.eventStore.Save(event) if err != nil { return nil, err } diff --git a/management/server/activity/event.go b/management/server/activity/event.go index d39083c6d..85fc4d3a5 100644 --- a/management/server/activity/event.go +++ b/management/server/activity/event.go @@ -3,43 +3,69 @@ package activity import "time" const ( - // DeviceEvent describes an event that happened of a device (e.g, connected/disconnected) - DeviceEvent Type = "device" - // ManagementEvent describes an event that happened on a Management service (e.g., user added) - ManagementEvent Type = "management" + // PeerAddedByUser indicates that a user added a new peer to the system + PeerAddedByUser Activity = iota + // PeerAddedWithSetupKey indicates that a new peer joined the system using a setup key + PeerAddedWithSetupKey + // UserJoined indicates that a new user joined the account + UserJoined + // UserInvited indicates that a new user was invited to join the account + UserInvited + // AccountCreated indicates that a new account has been created + AccountCreated ) const ( - AddPeerByUserOperation Operation = iota - AddPeerWithKeyOperation - UserJoinedOperation + // PeerAddedByUserMessage is a human-readable text message of the PeerAddedByUser activity + PeerAddedByUserMessage string = "New peer added by a user" + // PeerAddedWithSetupKeyMessage is a human-readable text message of the PeerAddedWithSetupKey activity + PeerAddedWithSetupKeyMessage = "New peer added with a setup key" + //UserJoinedMessage is a human-readable text message of the UserJoined activity + UserJoinedMessage string = "New user joined" + //UserInvitedMessage is a human-readable text message of the UserInvited activity + UserInvitedMessage string = "New user invited" + //AccountCreatedMessage is a human-readable text message of the AccountCreated activity + AccountCreatedMessage string = "Account created" ) -const ( - AddPeerByUserOperationMessage string = "Add new peer" - AddPeerWithKeyOperationMessage string = AddPeerByUserOperationMessage - UserJoinedOperationMessage string = "New user joined" -) +// Activity that triggered an Event +type Activity int -// MessageForOperation returns a string message for an Operation -func MessageForOperation(op Operation) string { - switch op { - case AddPeerByUserOperation: - return AddPeerByUserOperationMessage - case AddPeerWithKeyOperation: - return AddPeerWithKeyOperationMessage - case UserJoinedOperation: - return UserJoinedOperationMessage +// Message returns a string representation of an activity +func (a Activity) Message() string { + switch a { + case PeerAddedByUser: + return PeerAddedByUserMessage + case PeerAddedWithSetupKey: + return PeerAddedWithSetupKeyMessage + case UserJoined: + return UserJoinedMessage + case UserInvited: + return UserInvitedMessage + case AccountCreated: + return AccountCreatedMessage default: - return "UNKNOWN_OPERATION" + return "UNKNOWN_ACTIVITY" } } -// Type of the Event -type Type string - -// Operation is an action that triggered an Event -type Operation int +// StringCode returns a string code of the activity +func (a Activity) StringCode() string { + switch a { + case PeerAddedByUser: + return "user.peer.add" + case PeerAddedWithSetupKey: + return "setupkey.peer.add" + case UserJoined: + return "user.join" + case UserInvited: + return "user.invite" + case AccountCreated: + return "account.create" + default: + return "UNKNOWN_ACTIVITY" + } +} // Store provides an interface to store or stream events. type Store interface { @@ -55,31 +81,26 @@ type Store interface { type Event struct { // Timestamp of the event Timestamp time.Time - // Operation that was performed during the event - Operation string - // OperationCode that was performed during the event - OperationCode Operation + // Activity that was performed during the event + Activity Activity // ID of the event (can be empty, meaning that it wasn't yet generated) ID uint64 - // Type of the event - Type Type - // ModifierID is the ID of an object that modifies a Target - ModifierID string - // TargetID is the ID of an object that a Modifier modifies + // InitiatorID is the ID of an object that initiated the event (e.g., a user) + InitiatorID string + // TargetID is the ID of an object that was effected by the event (e.g., a peer) TargetID string - // AccountID where event happened + // AccountID is the ID of an account where the event happened AccountID string } // Copy the event func (e *Event) Copy() *Event { return &Event{ - Timestamp: e.Timestamp, - Operation: e.Operation, - ID: e.ID, - Type: e.Type, - ModifierID: e.ModifierID, - TargetID: e.TargetID, - AccountID: e.AccountID, + Timestamp: e.Timestamp, + Activity: e.Activity, + ID: e.ID, + InitiatorID: e.InitiatorID, + TargetID: e.TargetID, + AccountID: e.AccountID, } } diff --git a/management/server/activity/sqlite.go b/management/server/activity/sqlite.go index 626821fac..c46a633bb 100644 --- a/management/server/activity/sqlite.go +++ b/management/server/activity/sqlite.go @@ -11,12 +11,12 @@ import ( const ( SQLiteEventSinkDB = "events.db" createTableQuery = "CREATE TABLE IF NOT EXISTS events " + - "(id INTEGER PRIMARY KEY AUTOINCREMENT, account TEXT NOT NULL, " + - "operation INTEGER, " + - "type TEXT, " + + "(id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "activity INTEGER, " + "timestamp DATETIME, " + - "modifier TEXT," + - " target TEXT);" + "initiator_id TEXT," + + "account_id TEXT," + + " target_id TEXT);" ) // SQLiteStore is the implementation of the activity.Store interface backed by SQLite @@ -44,26 +44,23 @@ func processResult(result *sql.Rows) ([]*Event, error) { events := make([]*Event, 0) for result.Next() { var id int64 - var operation Operation + var operation Activity var timestamp time.Time - var modifier string + var initiator string var target string var account string - var typ Type - err := result.Scan(&id, &operation, ×tamp, &modifier, &target, &account, &typ) + err := result.Scan(&id, &operation, ×tamp, &initiator, &target, &account) if err != nil { return nil, err } events = append(events, &Event{ - Timestamp: timestamp, - OperationCode: operation, - Operation: MessageForOperation(operation), - ID: uint64(id), - Type: typ, - ModifierID: modifier, - TargetID: target, - AccountID: account, + Timestamp: timestamp, + Activity: operation, + ID: uint64(id), + InitiatorID: initiator, + TargetID: target, + AccountID: account, }) } @@ -76,8 +73,8 @@ func (store *SQLiteStore) Get(accountID string, offset, limit int, descending bo if !descending { order = "ASC" } - stmt, err := store.db.Prepare(fmt.Sprintf("SELECT id, operation, timestamp, modifier, target, account, type"+ - " FROM events WHERE account = ? ORDER BY timestamp %s LIMIT ? OFFSET ?;", order)) + stmt, err := store.db.Prepare(fmt.Sprintf("SELECT id, activity, timestamp, initiator_id, target_id, account_id"+ + " FROM events WHERE account_id = ? ORDER BY timestamp %s LIMIT ? OFFSET ?;", order)) if err != nil { return nil, err } @@ -94,12 +91,12 @@ func (store *SQLiteStore) Get(accountID string, offset, limit int, descending bo // Save an event in the SQLite events table func (store *SQLiteStore) Save(event *Event) (*Event, error) { - stmt, err := store.db.Prepare("INSERT INTO events(operation, timestamp, modifier, target, account, type) VALUES(?, ?, ?, ?, ?, ?)") + stmt, err := store.db.Prepare("INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id) VALUES(?, ?, ?, ?, ?)") if err != nil { return nil, err } - result, err := stmt.Exec(event.OperationCode, event.Timestamp, event.ModifierID, event.TargetID, event.AccountID, event.Type) + result, err := stmt.Exec(event.Activity, event.Timestamp, event.InitiatorID, event.TargetID, event.AccountID) if err != nil { return nil, err } diff --git a/management/server/activity/sqlite_test.go b/management/server/activity/sqlite_test.go index df27f782a..4fb7d76ef 100644 --- a/management/server/activity/sqlite_test.go +++ b/management/server/activity/sqlite_test.go @@ -19,12 +19,11 @@ func TestNewSQLiteStore(t *testing.T) { for i := 0; i < 10; i++ { _, err = store.Save(&Event{ - Timestamp: time.Now(), - OperationCode: AddPeerByUserOperation, - Type: ManagementEvent, - ModifierID: "user_" + fmt.Sprint(i), - TargetID: "peer_" + fmt.Sprint(i), - AccountID: accountID, + Timestamp: time.Now(), + Activity: PeerAddedByUser, + InitiatorID: "user_" + fmt.Sprint(i), + TargetID: "peer_" + fmt.Sprint(i), + AccountID: accountID, }) if err != nil { t.Fatal(err) diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index e70f9ebc7..3bdba4be5 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -47,12 +47,12 @@ components: items: type: string required: - - id - - email - - name - - role - - auto_groups - - status + - id + - email + - name + - role + - auto_groups + - status UserRequest: type: object properties: @@ -98,8 +98,8 @@ components: description: Peer's hostname type: string required: - - id - - name + - id + - name Peer: allOf: - $ref: '#/components/schemas/PeerMinimum' @@ -142,15 +142,15 @@ components: description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud type: string required: - - ip - - connected - - last_seen - - os - - version - - groups - - ssh_enabled - - hostname - - dns_label + - ip + - connected + - last_seen + - os + - version + - groups + - ssh_enabled + - hostname + - dns_label SetupKey: type: object properties: @@ -199,19 +199,19 @@ components: description: A number of times this key can be used. The value of 0 indicates the unlimited usage. type: integer required: - - id - - key - - name - - expires - - type - - valid - - revoked - - used_times - - last_used - - state - - auto_groups - - updated_at - - usage_limit + - id + - key + - name + - expires + - type + - valid + - revoked + - used_times + - last_used + - state + - auto_groups + - updated_at + - usage_limit SetupKeyRequest: type: object properties: @@ -255,9 +255,9 @@ components: description: Count of peers associated to the group type: integer required: - - id - - name - - peers_count + - id + - name + - peers_count Group: allOf: - $ref: '#/components/schemas/GroupMinimum' @@ -269,7 +269,7 @@ components: items: $ref: '#/components/schemas/PeerMinimum' required: - - peers + - peers PatchMinimum: type: object properties: @@ -313,10 +313,10 @@ components: description: Rule flow, currently, only "bidirect" for bi-directional traffic is accepted type: string required: - - name - - description - - disabled - - flow + - name + - description + - disabled + - flow Rule: allOf: - type: object @@ -325,7 +325,7 @@ components: description: Rule ID type: string required: - - id + - id - $ref: '#/components/schemas/RuleMinimum' - type: object properties: @@ -340,8 +340,8 @@ components: items: $ref: '#/components/schemas/GroupMinimum' required: - - sources - - destinations + - sources + - destinations RulePatchOperation: allOf: - $ref: '#/components/schemas/PatchMinimum' @@ -430,7 +430,7 @@ components: ns_type: description: Nameserver Type type: string - enum: ["udp"] + enum: [ "udp" ] port: description: Nameserver Port type: integer @@ -500,7 +500,7 @@ components: path: description: Nameserver group field to update in form / type: string - enum: [ "name", "description", "enabled", "groups", "nameservers", "primary", "domains" ] + enum: [ "name", "description", "enabled", "groups", "nameservers", "primary", "domains" ] required: - path Event: @@ -513,16 +513,13 @@ components: description: The date and time when the event occurred type: string format: date-time - operation: - description: The operation (or action) that occurred during the event + activity: + description: The activity that occurred during the event type: string - operation_code: - description: The numeric code of the operation (or action) that occurred during the event - type: integer - type: - description: The type of the event that occurred. Indicates whether it was a management or device event + activity_code: + description: The string code of the activity that occurred during the event type: string - enum: ["device", "management"] + enum: [ "account.create", "user.join", "user.invite", "user.peer.add", "setupkey.peer.add" ] initiator_id: description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event. type: string @@ -532,33 +529,32 @@ components: required: - id - timestamp - - operation - - operation_code - - type + - activity + - activity_code - initiator_id - target_id responses: not_found: description: Resource not found - content: {} + content: { } validation_failed_simple: description: Validation failed - content: {} + content: { } bad_request: description: Bad Request - content: {} + content: { } internal_error: description: Internal Server Error content: { } validation_failed: description: Validation failed - content: {} + content: { } forbidden: description: Forbidden - content: {} + content: { } requires_authentication: description: Requires authentication - content: {} + content: { } securitySchemes: BearerAuth: type: http @@ -570,9 +566,9 @@ paths: /api/users: get: summary: Returns a list of all users - tags: [Users] + tags: [ Users ] security: - - BearerAuth: [] + - BearerAuth: [ ] responses: '200': description: A JSON array of Users @@ -593,7 +589,7 @@ paths: /api/users/: post: summary: Create a User (invite) - tags: [ Users] + tags: [ Users ] security: - BearerAuth: [ ] requestBody: @@ -620,7 +616,7 @@ paths: /api/users/{id}: put: summary: Update information about a User - tags: [ Users] + tags: [ Users ] security: - BearerAuth: [ ] parameters: @@ -654,9 +650,9 @@ paths: /api/peers: get: summary: Returns a list of all peers - tags: [Peers] + tags: [ Peers ] security: - - BearerAuth: [] + - BearerAuth: [ ] responses: '200': description: A JSON Array of Peers @@ -677,7 +673,7 @@ paths: /api/peers/{id}: get: summary: Get information about a peer - tags: [Peers] + tags: [ Peers ] security: - BearerAuth: [ ] parameters: @@ -704,7 +700,7 @@ paths: "$ref": "#/components/responses/internal_error" put: summary: Update information about a peer - tags: [Peers] + tags: [ Peers ] security: - BearerAuth: [ ] parameters: @@ -745,7 +741,7 @@ paths: "$ref": "#/components/responses/internal_error" delete: summary: Delete a peer - tags: [Peers] + tags: [ Peers ] security: - BearerAuth: [ ] parameters: @@ -758,7 +754,7 @@ paths: responses: '200': description: Delete status code - content: {} + content: { } '400': "$ref": "#/components/responses/bad_request" '401': @@ -770,7 +766,7 @@ paths: /api/setup-keys: get: summary: Returns a list of all Setup Keys - tags: [Setup Keys] + tags: [ Setup Keys ] security: - BearerAuth: [ ] responses: @@ -792,7 +788,7 @@ paths: "$ref": "#/components/responses/internal_error" post: summary: Creates a Setup Key - tags: [Setup Keys] + tags: [ Setup Keys ] security: - BearerAuth: [ ] requestBody: @@ -819,7 +815,7 @@ paths: /api/setup-keys/{id}: get: summary: Get information about a Setup Key - tags: [Setup Keys] + tags: [ Setup Keys ] security: - BearerAuth: [ ] parameters: @@ -846,7 +842,7 @@ paths: "$ref": "#/components/responses/internal_error" put: summary: Update information about a Setup Key - tags: [Setup Keys] + tags: [ Setup Keys ] security: - BearerAuth: [ ] parameters: @@ -879,7 +875,7 @@ paths: "$ref": "#/components/responses/internal_error" delete: summary: Delete a Setup Key - tags: [Setup Keys] + tags: [ Setup Keys ] security: - BearerAuth: [ ] parameters: @@ -892,7 +888,7 @@ paths: responses: '200': description: Delete status code - content: {} + content: { } '400': "$ref": "#/components/responses/bad_request" '401': @@ -904,7 +900,7 @@ paths: /api/groups: get: summary: Returns a list of all Groups - tags: [Groups] + tags: [ Groups ] security: - BearerAuth: [ ] responses: @@ -926,7 +922,7 @@ paths: "$ref": "#/components/responses/internal_error" post: summary: Creates a Group - tags: [Groups] + tags: [ Groups ] security: - BearerAuth: [ ] requestBody: @@ -962,7 +958,7 @@ paths: /api/groups/{id}: get: summary: Get information about a Group - tags: [Groups] + tags: [ Groups ] security: - BearerAuth: [ ] parameters: @@ -989,7 +985,7 @@ paths: "$ref": "#/components/responses/internal_error" put: summary: Update/Replace a Group - tags: [Groups] + tags: [ Groups ] security: - BearerAuth: [ ] parameters: @@ -1064,7 +1060,7 @@ paths: "$ref": "#/components/responses/internal_error" delete: summary: Delete a Group - tags: [Groups] + tags: [ Groups ] security: - BearerAuth: [ ] parameters: @@ -1077,7 +1073,7 @@ paths: responses: '200': description: Delete status code - content: {} + content: { } '400': "$ref": "#/components/responses/bad_request" '401': @@ -1089,7 +1085,7 @@ paths: /api/rules: get: summary: Returns a list of all Rules - tags: [Rules] + tags: [ Rules ] security: - BearerAuth: [ ] responses: @@ -1111,7 +1107,7 @@ paths: "$ref": "#/components/responses/internal_error" post: summary: Creates a Rule - tags: [Rules] + tags: [ Rules ] security: - BearerAuth: [ ] requestBody: @@ -1141,7 +1137,7 @@ paths: /api/rules/{id}: get: summary: Get information about a Rules - tags: [Rules] + tags: [ Rules ] security: - BearerAuth: [ ] parameters: @@ -1168,7 +1164,7 @@ paths: "$ref": "#/components/responses/internal_error" put: summary: Update/Replace a Rule - tags: [Rules] + tags: [ Rules ] security: - BearerAuth: [ ] parameters: @@ -1247,7 +1243,7 @@ paths: "$ref": "#/components/responses/internal_error" delete: summary: Delete a Rule - tags: [Rules] + tags: [ Rules ] security: - BearerAuth: [ ] parameters: @@ -1260,7 +1256,7 @@ paths: responses: '200': description: Delete status code - content: {} + content: { } '400': "$ref": "#/components/responses/bad_request" '401': diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index a4b82d3b6..99f929a6a 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -11,10 +11,13 @@ const ( BearerAuthScopes = "BearerAuth.Scopes" ) -// Defines values for EventType. +// Defines values for EventActivityCode. const ( - EventTypeDevice EventType = "device" - EventTypeManagement EventType = "management" + EventActivityCodeAccountCreate EventActivityCode = "account.create" + EventActivityCodeSetupkeyPeerAdd EventActivityCode = "setupkey.peer.add" + EventActivityCodeUserInvite EventActivityCode = "user.invite" + EventActivityCodeUserJoin EventActivityCode = "user.join" + EventActivityCodeUserPeerAdd EventActivityCode = "user.peer.add" ) // Defines values for GroupPatchOperationOp. @@ -105,30 +108,27 @@ const ( // Event defines model for Event. type Event struct { + // Activity The activity that occurred during the event + Activity string `json:"activity"` + + // ActivityCode The string code of the activity that occurred during the event + ActivityCode EventActivityCode `json:"activity_code"` + // Id Event unique identifier Id string `json:"id"` // InitiatorId The ID of the initiator of the event. E.g., an ID of a user that triggered the event. InitiatorId string `json:"initiator_id"` - // Operation The operation (or action) that occurred during the event - Operation string `json:"operation"` - - // OperationCode The numeric code of the operation (or action) that occurred during the event - OperationCode int `json:"operation_code"` - // TargetId The ID of the target of the event. E.g., an ID of the peer that a user removed. TargetId string `json:"target_id"` // Timestamp The date and time when the event occurred Timestamp time.Time `json:"timestamp"` - - // Type The type of the event that occurred. Indicates whether it was a management or device event - Type EventType `json:"type"` } -// EventType The type of the event that occurred. Indicates whether it was a management or device event -type EventType string +// EventActivityCode The string code of the activity that occurred during the event +type EventActivityCode string // Group defines model for Group. type Group struct { diff --git a/management/server/http/events.go b/management/server/http/events.go index e3f88c983..af332af4d 100644 --- a/management/server/http/events.go +++ b/management/server/http/events.go @@ -42,7 +42,7 @@ func (h *Events) GetEvents(w http.ResponseWriter, r *http.Request) { util.WriteError(err, w) return } - var events []*api.Event + events := make([]*api.Event, 0) for _, e := range accountEvents { events = append(events, toEventResponse(e)) } @@ -53,12 +53,11 @@ func (h *Events) GetEvents(w http.ResponseWriter, r *http.Request) { func toEventResponse(event *activity.Event) *api.Event { return &api.Event{ - Id: fmt.Sprint(event.ID), - InitiatorId: event.ModifierID, - Operation: event.Operation, - OperationCode: int(event.OperationCode), - TargetId: event.TargetID, - Timestamp: event.Timestamp, - Type: api.EventType(event.Type), + Id: fmt.Sprint(event.ID), + InitiatorId: event.InitiatorID, + Activity: event.Activity.Message(), + ActivityCode: api.EventActivityCode(event.Activity.StringCode()), + TargetId: event.TargetID, + Timestamp: event.Timestamp, } } diff --git a/management/server/peer.go b/management/server/peer.go index a3ade59a6..268ba0d16 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -363,7 +363,6 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* opEvent := &activity.Event{ Timestamp: time.Now(), - Type: activity.ManagementEvent, AccountID: account.Id, } @@ -379,11 +378,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* } account.SetupKeys[sk.Key] = sk.IncrementUsage() - opEvent.ModifierID = sk.Id - opEvent.OperationCode = activity.AddPeerWithKeyOperation + opEvent.InitiatorID = sk.Id + opEvent.Activity = activity.PeerAddedWithSetupKey } else { - opEvent.ModifierID = userID - opEvent.OperationCode = activity.AddPeerByUserOperation + opEvent.InitiatorID = userID + opEvent.Activity = activity.PeerAddedByUser } takenIps := account.getTakenIPs() diff --git a/management/server/user.go b/management/server/user.go index 82775c726..17ba808a1 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -3,10 +3,9 @@ package server import ( "fmt" "github.com/netbirdio/netbird/management/server/idp" + "github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/status" "strings" - - "github.com/netbirdio/netbird/management/server/jwtclaims" ) const (