diff --git a/management/server/idp/zitadel.go b/management/server/idp/zitadel.go index e42b9a506..f1c6f1579 100644 --- a/management/server/idp/zitadel.go +++ b/management/server/idp/zitadel.go @@ -1,13 +1,10 @@ package idp import ( - "encoding/base64" - "encoding/json" "fmt" "io" "net/http" "net/url" - "strconv" "strings" "sync" "time" @@ -68,12 +65,6 @@ type zitadelUser struct { type zitadelAttributes map[string][]map[string]any -// zitadelMetadata holds additional user data. -type zitadelMetadata struct { - Key string `json:"key"` - Value string `json:"value"` -} - // zitadelProfile represents an zitadel user profile response. type zitadelProfile struct { ID string `json:"id"` @@ -82,7 +73,6 @@ type zitadelProfile struct { PreferredLoginName string `json:"preferredLoginName"` LoginNames []string `json:"loginNames"` Human *zitadelUser `json:"human"` - Metadata []zitadelMetadata } // NewZitadelManager creates a new instance of the ZitadelManager. @@ -235,42 +225,8 @@ func (zc *ZitadelCredentials) Authenticate() (JWTToken, error) { } // CreateUser creates a new user in zitadel Idp and sends an invite. -func (zm *ZitadelManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) { - payload, err := buildZitadelCreateUserRequestPayload(email, name) - if err != nil { - return nil, err - } - - body, err := zm.post("users/human/_import", payload) - if err != nil { - return nil, err - } - - if zm.appMetrics != nil { - zm.appMetrics.IDPMetrics().CountCreateUser() - } - - var result struct { - UserID string `json:"userId"` - } - err = zm.helper.Unmarshal(body, &result) - if err != nil { - return nil, err - } - - invite := true - appMetadata := AppMetadata{ - WTAccountID: accountID, - WTPendingInvite: &invite, - } - - // Add metadata to new user - err = zm.UpdateUserAppMetadata(result.UserID, appMetadata) - if err != nil { - return nil, err - } - - return zm.GetUserDataByID(result.UserID, appMetadata) +func (zm *ZitadelManager) CreateUser(_, _, _, _ string) (*UserData, error) { + return nil, fmt.Errorf("method CreateUser not implemented") } // GetUserByEmail searches users with a given email. @@ -308,12 +264,6 @@ func (zm *ZitadelManager) GetUserByEmail(email string) ([]*UserData, error) { users := make([]*UserData, 0) for _, profile := range profiles.Result { - metadata, err := zm.getUserMetadata(profile.ID) - if err != nil { - return nil, err - } - profile.Metadata = metadata - users = append(users, profile.userData()) } @@ -337,18 +287,15 @@ func (zm *ZitadelManager) GetUserDataByID(userID string, appMetadata AppMetadata return nil, err } - metadata, err := zm.getUserMetadata(userID) - if err != nil { - return nil, err - } - profile.User.Metadata = metadata + userData := profile.User.userData() + userData.AppMetadata = appMetadata - return profile.User.userData(), nil + return userData, nil } // GetAccount returns all the users for a given profile. -func (zm *ZitadelManager) GetAccount(accountID string) ([]*UserData, error) { - accounts, err := zm.GetAllAccounts() +func (zm *ZitadelManager) GetAccount(_ string) ([]*UserData, error) { + body, err := zm.post("users/_search", "") if err != nil { return nil, err } @@ -357,7 +304,18 @@ func (zm *ZitadelManager) GetAccount(accountID string) ([]*UserData, error) { zm.appMetrics.IDPMetrics().CountGetAccount() } - return accounts[accountID], nil + var profiles struct{ Result []zitadelProfile } + err = zm.helper.Unmarshal(body, &profiles) + if err != nil { + return nil, err + } + + users := make([]*UserData, 0) + for _, profile := range profiles.Result { + users = append(users, profile.userData()) + } + + return users, nil } // GetAllAccounts gets all registered accounts with corresponding user data. @@ -380,22 +338,8 @@ func (zm *ZitadelManager) GetAllAccounts() (map[string][]*UserData, error) { indexedUsers := make(map[string][]*UserData) for _, profile := range profiles.Result { - // fetch user metadata - metadata, err := zm.getUserMetadata(profile.ID) - if err != nil { - return nil, err - } - profile.Metadata = metadata - - userData := profile.userData() - accountID := userData.AppMetadata.WTAccountID - - if accountID != "" { - if _, ok := indexedUsers[accountID]; !ok { - indexedUsers[accountID] = make([]*UserData, 0) - } - indexedUsers[accountID] = append(indexedUsers[accountID], userData) - } + accountID := "unset" + indexedUsers[accountID] = append(indexedUsers[accountID], profile.userData()) } return indexedUsers, nil @@ -403,42 +347,7 @@ func (zm *ZitadelManager) GetAllAccounts() (map[string][]*UserData, error) { // UpdateUserAppMetadata updates user app metadata based on userID and metadata map. // Metadata values are base64 encoded. -func (zm *ZitadelManager) UpdateUserAppMetadata(userID string, appMetadata AppMetadata) error { - if appMetadata.WTPendingInvite == nil { - appMetadata.WTPendingInvite = new(bool) - } - pendingInviteBuf := strconv.AppendBool([]byte{}, *appMetadata.WTPendingInvite) - - wtAccountIDValue := base64.StdEncoding.EncodeToString([]byte(appMetadata.WTAccountID)) - wtPendingInviteValue := base64.StdEncoding.EncodeToString(pendingInviteBuf) - - metadata := zitadelAttributes{ - "metadata": { - { - "key": wtAccountID, - "value": wtAccountIDValue, - }, - { - "key": wtPendingInvite, - "value": wtPendingInviteValue, - }, - }, - } - payload, err := zm.helper.Marshal(metadata) - if err != nil { - return err - } - - resource := fmt.Sprintf("users/%s/metadata/_bulk", userID) - _, err = zm.post(resource, string(payload)) - if err != nil { - return err - } - - if zm.appMetrics != nil { - zm.appMetrics.IDPMetrics().CountUpdateUserAppMetadata() - } - +func (zm *ZitadelManager) UpdateUserAppMetadata(_ string, _ AppMetadata) error { return nil } @@ -460,24 +369,6 @@ func (zm *ZitadelManager) DeleteUser(userID string) error { } return nil - -} - -// getUserMetadata requests user metadata from zitadel via ID. -func (zm *ZitadelManager) getUserMetadata(userID string) ([]zitadelMetadata, error) { - resource := fmt.Sprintf("users/%s/metadata/_search", userID) - body, err := zm.post(resource, "") - if err != nil { - return nil, err - } - - var metadata struct{ Result []zitadelMetadata } - err = zm.helper.Unmarshal(body, &metadata) - if err != nil { - return nil, err - } - - return metadata.Result, nil } // post perform Post requests. @@ -517,38 +408,7 @@ func (zm *ZitadelManager) post(resource string, body string) ([]byte, error) { } // delete perform Delete requests. -func (zm *ZitadelManager) delete(resource string) error { - jwtToken, err := zm.credentials.Authenticate() - if err != nil { - return err - } - - reqURL := fmt.Sprintf("%s/%s", zm.managementEndpoint, resource) - req, err := http.NewRequest(http.MethodDelete, reqURL, nil) - if err != nil { - return err - } - req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken) - req.Header.Add("content-type", "application/json") - - resp, err := zm.httpClient.Do(req) - if err != nil { - if zm.appMetrics != nil { - zm.appMetrics.IDPMetrics().CountRequestError() - } - - return err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - if zm.appMetrics != nil { - zm.appMetrics.IDPMetrics().CountRequestStatusError() - } - - return fmt.Errorf("unable to delete %s, statusCode %d", reqURL, resp.StatusCode) - } - +func (zm *ZitadelManager) delete(_ string) error { return nil } @@ -588,38 +448,13 @@ func (zm *ZitadelManager) get(resource string, q url.Values) ([]byte, error) { return io.ReadAll(resp.Body) } -// value returns string represented by the base64 string value. -func (zm zitadelMetadata) value() string { - value, err := base64.StdEncoding.DecodeString(zm.Value) - if err != nil { - return "" - } - - return string(value) -} - // userData construct user data from zitadel profile. func (zp zitadelProfile) userData() *UserData { var ( - email string - name string - wtAccountIDValue string - wtPendingInviteValue bool + email string + name string ) - for _, metadata := range zp.Metadata { - if metadata.Key == wtAccountID { - wtAccountIDValue = metadata.value() - } - - if metadata.Key == wtPendingInvite { - value, err := strconv.ParseBool(metadata.value()) - if err == nil { - wtPendingInviteValue = value - } - } - } - // Obtain the email for the human account and the login name, // for the machine account. if zp.Human != nil { @@ -636,39 +471,5 @@ func (zp zitadelProfile) userData() *UserData { Email: email, Name: name, ID: zp.ID, - AppMetadata: AppMetadata{ - WTAccountID: wtAccountIDValue, - WTPendingInvite: &wtPendingInviteValue, - }, } } - -func buildZitadelCreateUserRequestPayload(email string, name string) (string, error) { - var firstName, lastName string - - words := strings.Fields(name) - if n := len(words); n > 0 { - firstName = strings.Join(words[:n-1], " ") - lastName = words[n-1] - } - - req := &zitadelUser{ - UserName: name, - Profile: zitadelUserInfo{ - FirstName: strings.TrimSpace(firstName), - LastName: strings.TrimSpace(lastName), - DisplayName: name, - }, - Email: zitadelEmail{ - Email: email, - IsEmailVerified: false, - }, - } - - str, err := json.Marshal(req) - if err != nil { - return "", err - } - - return string(str), nil -} diff --git a/management/server/idp/zitadel_test.go b/management/server/idp/zitadel_test.go index b558bba73..9931af7af 100644 --- a/management/server/idp/zitadel_test.go +++ b/management/server/idp/zitadel_test.go @@ -7,9 +7,10 @@ import ( "testing" "time" - "github.com/netbirdio/netbird/management/server/telemetry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/management/server/telemetry" ) func TestNewZitadelManager(t *testing.T) { @@ -296,98 +297,6 @@ func TestZitadelAuthenticate(t *testing.T) { } } -func TestZitadelUpdateUserAppMetadata(t *testing.T) { - type updateUserAppMetadataTest struct { - name string - inputReqBody string - expectedReqBody string - appMetadata AppMetadata - statusCode int - helper ManagerHelper - managerCreds ManagerCredentials - assertErrFunc assert.ErrorAssertionFunc - assertErrFuncMessage string - } - - appMetadata := AppMetadata{WTAccountID: "ok"} - - updateUserAppMetadataTestCase1 := updateUserAppMetadataTest{ - name: "Bad Authentication", - expectedReqBody: "", - appMetadata: appMetadata, - statusCode: 400, - helper: JsonParser{}, - managerCreds: &mockZitadelCredentials{ - jwtToken: JWTToken{}, - err: fmt.Errorf("error"), - }, - assertErrFunc: assert.Error, - assertErrFuncMessage: "should return error", - } - - updateUserAppMetadataTestCase2 := updateUserAppMetadataTest{ - name: "Bad Response Parsing", - statusCode: 400, - helper: &mockJsonParser{marshalErrorString: "error"}, - managerCreds: &mockZitadelCredentials{ - jwtToken: JWTToken{}, - }, - assertErrFunc: assert.Error, - assertErrFuncMessage: "should return error", - } - - updateUserAppMetadataTestCase3 := updateUserAppMetadataTest{ - name: "Good request", - expectedReqBody: "{\"metadata\":[{\"key\":\"wt_account_id\",\"value\":\"b2s=\"},{\"key\":\"wt_pending_invite\",\"value\":\"ZmFsc2U=\"}]}", - appMetadata: appMetadata, - statusCode: 200, - helper: JsonParser{}, - managerCreds: &mockZitadelCredentials{ - jwtToken: JWTToken{}, - }, - assertErrFunc: assert.NoError, - assertErrFuncMessage: "shouldn't return error", - } - - invite := true - updateUserAppMetadataTestCase4 := updateUserAppMetadataTest{ - name: "Update Pending Invite", - expectedReqBody: "{\"metadata\":[{\"key\":\"wt_account_id\",\"value\":\"b2s=\"},{\"key\":\"wt_pending_invite\",\"value\":\"dHJ1ZQ==\"}]}", - appMetadata: AppMetadata{ - WTAccountID: "ok", - WTPendingInvite: &invite, - }, - statusCode: 200, - helper: JsonParser{}, - managerCreds: &mockZitadelCredentials{ - jwtToken: JWTToken{}, - }, - assertErrFunc: assert.NoError, - assertErrFuncMessage: "shouldn't return error", - } - - for _, testCase := range []updateUserAppMetadataTest{updateUserAppMetadataTestCase1, updateUserAppMetadataTestCase2, - updateUserAppMetadataTestCase3, updateUserAppMetadataTestCase4} { - t.Run(testCase.name, func(t *testing.T) { - reqClient := mockHTTPClient{ - resBody: testCase.inputReqBody, - code: testCase.statusCode, - } - - manager := &ZitadelManager{ - httpClient: &reqClient, - credentials: testCase.managerCreds, - helper: testCase.helper, - } - - err := manager.UpdateUserAppMetadata("1", testCase.appMetadata) - testCase.assertErrFunc(t, err, testCase.assertErrFuncMessage) - - assert.Equal(t, testCase.expectedReqBody, reqClient.reqBody, "request body should match") - }) - } -} - func TestZitadelProfile(t *testing.T) { type azureProfileTest struct { name string @@ -418,16 +327,6 @@ func TestZitadelProfile(t *testing.T) { IsEmailVerified: true, }, }, - Metadata: []zitadelMetadata{ - { - Key: "wt_account_id", - Value: "MQ==", - }, - { - Key: "wt_pending_invite", - Value: "ZmFsc2U=", - }, - }, }, expectedUserData: UserData{ ID: "test1", @@ -451,16 +350,6 @@ func TestZitadelProfile(t *testing.T) { "machine", }, Human: nil, - Metadata: []zitadelMetadata{ - { - Key: "wt_account_id", - Value: "MQ==", - }, - { - Key: "wt_pending_invite", - Value: "dHJ1ZQ==", - }, - }, }, expectedUserData: UserData{ ID: "test2", @@ -480,8 +369,6 @@ func TestZitadelProfile(t *testing.T) { assert.Equal(t, testCase.expectedUserData.ID, userData.ID, "User id should match") assert.Equal(t, testCase.expectedUserData.Email, userData.Email, "User email should match") assert.Equal(t, testCase.expectedUserData.Name, userData.Name, "User name should match") - assert.Equal(t, testCase.expectedUserData.AppMetadata.WTAccountID, userData.AppMetadata.WTAccountID, "Account id should match") - assert.Equal(t, testCase.expectedUserData.AppMetadata.WTPendingInvite, userData.AppMetadata.WTPendingInvite, "Pending invite should match") }) } }