From 50ecf6f4da351c3305ec03b3270ba5f15db01f33 Mon Sep 17 00:00:00 2001 From: bcmmbaga Date: Thu, 14 Sep 2023 12:49:35 +0300 Subject: [PATCH] wip: Handle user metadata without transmitting them to Identity Provider --- management/server/account.go | 32 ++++ management/server/idp/authentik.go | 231 +++++++++++++++-------------- management/server/idp/idp.go | 8 + 3 files changed, 158 insertions(+), 113 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index 4c707af3a..d08e25fd4 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -4,6 +4,7 @@ import ( "context" "crypto/sha256" b64 "encoding/base64" + "errors" "fmt" "hash/crc32" "math/rand" @@ -938,6 +939,37 @@ func (am *DefaultAccountManager) newAccount(userID, domain string) (*Account, er func (am *DefaultAccountManager) warmupIDPCache() error { userData, err := am.idpManager.GetAllAccounts() if err != nil { + var idpErr *idp.Error + if errors.As(err, &idpErr) { + log.Warnf("failed to fetch all accounts from idp: %v", idpErr) + + log.Info("warming cache using store") + accounts := am.Store.GetAllAccounts() + + for _, account := range accounts { + data := make([]*idp.UserData, 0) + + for _, user := range account.Users { + // fetch user info with idp manager + userData, err := am.idpManager.GetUserDataByID(user.Id, idp.AppMetadata{WTAccountID: account.Id}) + if err != nil { + return err + } + + // update user metadata as are not stored in upstream idp + // Note (self-hosted does not support pendingInvite and invitedBy status) + userData.AppMetadata.WTAccountID = account.Id + data = append(data, userData) + } + + err = am.cacheManager.Set(am.ctx, account.Id, data, cacheStore.WithExpiration(cacheEntryExpiration())) + if err != nil { + return err + } + } + return nil + } + return err } diff --git a/management/server/idp/authentik.go b/management/server/idp/authentik.go index 0898f1c94..c57efef1a 100644 --- a/management/server/idp/authentik.go +++ b/management/server/idp/authentik.go @@ -12,9 +12,10 @@ import ( "time" "github.com/golang-jwt/jwt" - "github.com/netbirdio/netbird/management/server/telemetry" log "github.com/sirupsen/logrus" "goauthentik.io/api/v3" + + "github.com/netbirdio/netbird/management/server/telemetry" ) // AuthentikManager authentik manager client instance. @@ -210,47 +211,48 @@ func (ac *AuthentikCredentials) Authenticate() (JWTToken, error) { // UpdateUserAppMetadata updates user app metadata based on userID and metadata map. func (am *AuthentikManager) UpdateUserAppMetadata(userID string, appMetadata AppMetadata) error { - ctx, err := am.authenticationContext() - if err != nil { - return err - } - - userPk, err := strconv.ParseInt(userID, 10, 32) - if err != nil { - return err - } - - var pendingInvite bool - if appMetadata.WTPendingInvite != nil { - pendingInvite = *appMetadata.WTPendingInvite - } - - patchedUserReq := api.PatchedUserRequest{ - Attributes: map[string]interface{}{ - wtAccountID: appMetadata.WTAccountID, - wtPendingInvite: pendingInvite, - }, - } - _, resp, err := am.apiClient.CoreApi.CoreUsersPartialUpdate(ctx, int32(userPk)). - PatchedUserRequest(patchedUserReq). - Execute() - if err != nil { - return err - } - defer resp.Body.Close() - - if am.appMetrics != nil { - am.appMetrics.IDPMetrics().CountUpdateUserAppMetadata() - } - - if resp.StatusCode != http.StatusOK { - if am.appMetrics != nil { - am.appMetrics.IDPMetrics().CountRequestStatusError() - } - return fmt.Errorf("unable to update user %s, statusCode %d", userID, resp.StatusCode) - } - - return nil + //ctx, err := am.authenticationContext() + //if err != nil { + // return err + //} + // + //userPk, err := strconv.ParseInt(userID, 10, 32) + //if err != nil { + // return err + //} + // + //var pendingInvite bool + //if appMetadata.WTPendingInvite != nil { + // pendingInvite = *appMetadata.WTPendingInvite + //} + // + //patchedUserReq := api.PatchedUserRequest{ + // Attributes: map[string]interface{}{ + // wtAccountID: appMetadata.WTAccountID, + // wtPendingInvite: pendingInvite, + // }, + //} + //_, resp, err := am.apiClient.CoreApi.CoreUsersPartialUpdate(ctx, int32(userPk)). + // PatchedUserRequest(patchedUserReq). + // Execute() + //if err != nil { + // return err + //} + //defer resp.Body.Close() + // + //if am.appMetrics != nil { + // am.appMetrics.IDPMetrics().CountUpdateUserAppMetadata() + //} + // + //if resp.StatusCode != http.StatusOK { + // if am.appMetrics != nil { + // am.appMetrics.IDPMetrics().CountRequestStatusError() + // } + // return fmt.Errorf("unable to update user %s, statusCode %d", userID, resp.StatusCode) + //} + // + //return nil + return &Error{"UpdateUserAppMetadata is not implemented"} } // GetUserDataByID requests user data from authentik via ID. @@ -287,83 +289,86 @@ func (am *AuthentikManager) GetUserDataByID(userID string, appMetadata AppMetada // GetAccount returns all the users for a given profile. func (am *AuthentikManager) GetAccount(accountID string) ([]*UserData, error) { - ctx, err := am.authenticationContext() - if err != nil { - return nil, err - } - - accountFilter := fmt.Sprintf("{%q:%q}", wtAccountID, accountID) - userList, resp, err := am.apiClient.CoreApi.CoreUsersList(ctx).Attributes(accountFilter).Execute() - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if am.appMetrics != nil { - am.appMetrics.IDPMetrics().CountGetAccount() - } - - if resp.StatusCode != http.StatusOK { - if am.appMetrics != nil { - am.appMetrics.IDPMetrics().CountRequestStatusError() - } - return nil, fmt.Errorf("unable to get account %s users, statusCode %d", accountID, resp.StatusCode) - } - - users := make([]*UserData, 0) - for _, user := range userList.Results { - userData, err := parseAuthentikUser(user) - if err != nil { - return nil, err - } - users = append(users, userData) - } - - return users, nil + //ctx, err := am.authenticationContext() + //if err != nil { + // return nil, err + //} + // + //accountFilter := fmt.Sprintf("{%q:%q}", wtAccountID, accountID) + //userList, resp, err := am.apiClient.CoreApi.CoreUsersList(ctx).Attributes(accountFilter).Execute() + //if err != nil { + // return nil, err + //} + //defer resp.Body.Close() + // + //if am.appMetrics != nil { + // am.appMetrics.IDPMetrics().CountGetAccount() + //} + // + //if resp.StatusCode != http.StatusOK { + // if am.appMetrics != nil { + // am.appMetrics.IDPMetrics().CountRequestStatusError() + // } + // return nil, fmt.Errorf("unable to get account %s users, statusCode %d", accountID, resp.StatusCode) + //} + // + //users := make([]*UserData, 0) + //for _, user := range userList.Results { + // userData, err := parseAuthentikUser(user) + // if err != nil { + // return nil, err + // } + // users = append(users, userData) + //} + // + //return users, nil + return nil, &Error{"GetAccount is not implemented"} } // GetAllAccounts gets all registered accounts with corresponding user data. // It returns a list of users indexed by accountID. func (am *AuthentikManager) GetAllAccounts() (map[string][]*UserData, error) { - ctx, err := am.authenticationContext() - if err != nil { - return nil, err - } + //ctx, err := am.authenticationContext() + //if err != nil { + // return nil, err + //} + // + //userList, resp, err := am.apiClient.CoreApi.CoreUsersList(ctx).Execute() + //if err != nil { + // return nil, err + //} + //defer resp.Body.Close() + // + //if am.appMetrics != nil { + // am.appMetrics.IDPMetrics().CountGetAllAccounts() + //} + // + //if resp.StatusCode != http.StatusOK { + // if am.appMetrics != nil { + // am.appMetrics.IDPMetrics().CountRequestStatusError() + // } + // return nil, fmt.Errorf("unable to get all accounts, statusCode %d", resp.StatusCode) + //} + // + //indexedUsers := make(map[string][]*UserData) + //for _, user := range userList.Results { + // userData, err := parseAuthentikUser(user) + // if err != nil { + // return nil, err + // } + // + // accountID := userData.AppMetadata.WTAccountID + // if accountID != "" { + // if _, ok := indexedUsers[accountID]; !ok { + // indexedUsers[accountID] = make([]*UserData, 0) + // } + // indexedUsers[accountID] = append(indexedUsers[accountID], userData) + // } + //} + // + //return indexedUsers, nil - userList, resp, err := am.apiClient.CoreApi.CoreUsersList(ctx).Execute() - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if am.appMetrics != nil { - am.appMetrics.IDPMetrics().CountGetAllAccounts() - } - - if resp.StatusCode != http.StatusOK { - if am.appMetrics != nil { - am.appMetrics.IDPMetrics().CountRequestStatusError() - } - return nil, fmt.Errorf("unable to get all accounts, statusCode %d", resp.StatusCode) - } - - indexedUsers := make(map[string][]*UserData) - for _, user := range userList.Results { - userData, err := parseAuthentikUser(user) - if err != nil { - return nil, err - } - - accountID := userData.AppMetadata.WTAccountID - if accountID != "" { - if _, ok := indexedUsers[accountID]; !ok { - indexedUsers[accountID] = make([]*UserData, 0) - } - indexedUsers[accountID] = append(indexedUsers[accountID], userData) - } - } - - return indexedUsers, nil + return nil, &Error{"GetAllAccounts is not implemented"} } // CreateUser creates a new user in authentik Idp and sends an invitation. diff --git a/management/server/idp/idp.go b/management/server/idp/idp.go index 3c1f4c327..f3758743d 100644 --- a/management/server/idp/idp.go +++ b/management/server/idp/idp.go @@ -9,6 +9,14 @@ import ( "github.com/netbirdio/netbird/management/server/telemetry" ) +type Error struct { + message string +} + +func (e *Error) Error() string { + return e.message +} + // Manager idp manager interface type Manager interface { UpdateUserAppMetadata(userId string, appMetadata AppMetadata) error