[management] activity external user info (#3461)

This commit is contained in:
Pedro Maia Costa
2025-03-11 08:07:15 +00:00
committed by Pedro Costa
parent f9f47b0ad8
commit 1763a953f4
2 changed files with 152 additions and 59 deletions

View File

@@ -10,6 +10,8 @@ import (
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types"
)
func isEnabled() bool {
@@ -19,16 +21,12 @@ func isEnabled() bool {
// GetEvents returns a list of activity events of an account
func (am *DefaultAccountManager) GetEvents(ctx context.Context, accountID, userID string) ([]*activity.Event, error) {
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
if err != nil {
return nil, err
}
user, err := account.FindUser(userID)
if err != nil {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user); err != nil {
return nil, err
}
@@ -58,6 +56,11 @@ func (am *DefaultAccountManager) GetEvents(ctx context.Context, accountID, userI
filtered = append(filtered, event)
}
err = am.fillEventsWithUserInfo(ctx, events, accountID, user)
if err != nil {
return nil, err
}
return filtered, nil
}
@@ -79,3 +82,145 @@ func (am *DefaultAccountManager) StoreEvent(ctx context.Context, initiatorID, ta
}()
}
}
type eventUserInfo struct {
email string
name string
accountId string
}
func (am *DefaultAccountManager) fillEventsWithUserInfo(ctx context.Context, events []*activity.Event, accountId string, user *types.User) error {
eventUserInfo, err := am.getEventsUserInfo(ctx, events, accountId, user)
if err != nil {
return err
}
for _, event := range events {
initiatorUserInfo, ok := eventUserInfo[event.InitiatorID]
if !ok {
log.WithContext(ctx).Warnf("failed to resolve user info for initiator: %s", event.InitiatorID)
}
if event.InitiatorEmail == "" && ok {
event.InitiatorEmail = initiatorUserInfo.email
}
if event.InitiatorName == "" && ok {
// here to allowed to be empty because in the first release we did not store the name
event.InitiatorName = initiatorUserInfo.name
}
if ok && event.AccountID != initiatorUserInfo.accountId {
if event.Meta == nil {
event.Meta = make(map[string]any)
}
event.Meta["external"] = true
}
targetUserInfo, ok := eventUserInfo[event.TargetID]
if !ok {
continue
}
if event.Meta == nil {
event.Meta = make(map[string]any)
}
event.Meta["email"] = targetUserInfo.email
event.Meta["username"] = targetUserInfo.name
}
return nil
}
func (am *DefaultAccountManager) getEventsUserInfo(ctx context.Context, events []*activity.Event, accountId string, user *types.User) (map[string]eventUserInfo, error) {
accountUsers, err := am.Store.GetAccountUsers(ctx, store.LockingStrengthShare, accountId)
if err != nil {
return nil, err
}
// @note check whether using a external initiator user here is an issue
userInfos, err := am.BuildUserInfosForAccount(ctx, accountId, user.Id, accountUsers)
if err != nil {
return nil, err
}
eventUserInfos := make(map[string]eventUserInfo)
for i, k := range userInfos {
eventUserInfos[i] = eventUserInfo{
email: k.Email,
name: k.Name,
accountId: accountId,
}
}
externalUserIds := []string{}
for _, event := range events {
if _, ok := eventUserInfos[event.InitiatorID]; ok {
continue
}
if event.InitiatorID == activity.SystemInitiator ||
event.InitiatorID == accountId ||
event.Activity == activity.PeerAddedWithSetupKey {
// @todo other events to be excluded if never initiated by a user
continue
}
externalUserIds = append(externalUserIds, event.InitiatorID)
}
if len(externalUserIds) == 0 {
return eventUserInfos, nil
}
return am.getEventsExternalUserInfo(ctx, externalUserIds, eventUserInfos, user)
}
func (am *DefaultAccountManager) getEventsExternalUserInfo(ctx context.Context, externalUserIds []string, eventUserInfos map[string]eventUserInfo, user *types.User) (map[string]eventUserInfo, error) {
externalAccountId := ""
fetched := make(map[string]struct{})
externalUsers := []*types.User{}
for _, id := range externalUserIds {
if _, ok := fetched[id]; ok {
continue
}
externalUser, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, id)
if err != nil {
continue
}
if externalAccountId != "" && externalAccountId != externalUser.AccountID {
return nil, fmt.Errorf("multiple external user accounts in events")
}
if externalAccountId == "" {
externalAccountId = externalUser.AccountID
}
fetched[id] = struct{}{}
externalUsers = append(externalUsers, externalUser)
}
// if we couldn't determine an account, return what we have
if externalAccountId == "" {
log.WithContext(ctx).Warnf("failed to determine external user account from users: %v", externalUserIds)
return eventUserInfos, nil
}
externalUserInfos, err := am.BuildUserInfosForAccount(ctx, externalAccountId, user.Id, externalUsers)
if err != nil {
return nil, err
}
for i, k := range externalUserInfos {
eventUserInfos[i] = eventUserInfo{
email: k.Email,
name: k.Name,
accountId: externalAccountId,
}
}
return eventUserInfos, nil
}

View File

@@ -1,7 +1,6 @@
package events
import (
"context"
"fmt"
"net/http"
@@ -47,66 +46,15 @@ func (h *handler) getAllEvents(w http.ResponseWriter, r *http.Request) {
util.WriteError(r.Context(), err, w)
return
}
events := make([]*api.Event, len(accountEvents))
for i, e := range accountEvents {
events[i] = toEventResponse(e)
}
err = h.fillEventsWithUserInfo(r.Context(), events, accountID, userID)
if err != nil {
util.WriteError(r.Context(), err, w)
return
}
util.WriteJSONObject(r.Context(), w, events)
}
func (h *handler) fillEventsWithUserInfo(ctx context.Context, events []*api.Event, accountId, userId string) error {
// build email, name maps based on users
userInfos, err := h.accountManager.GetUsersFromAccount(ctx, accountId, userId)
if err != nil {
log.WithContext(ctx).Errorf("failed to get users from account: %s", err)
return err
}
emails := make(map[string]string)
names := make(map[string]string)
for _, ui := range userInfos {
emails[ui.ID] = ui.Email
names[ui.ID] = ui.Name
}
var ok bool
for _, event := range events {
// fill initiator
if event.InitiatorEmail == "" {
event.InitiatorEmail, ok = emails[event.InitiatorId]
if !ok {
log.WithContext(ctx).Warnf("failed to resolve email for initiator: %s", event.InitiatorId)
}
}
if event.InitiatorName == "" {
// here to allowed to be empty because in the first release we did not store the name
event.InitiatorName = names[event.InitiatorId]
}
// fill target meta
email, ok := emails[event.TargetId]
if !ok {
continue
}
event.Meta["email"] = email
username, ok := names[event.TargetId]
if !ok {
continue
}
event.Meta["username"] = username
}
return nil
}
func toEventResponse(event *activity.Event) *api.Event {
meta := make(map[string]string)
if event.Meta != nil {