diff --git a/controllers/organization_controller.go b/controllers/organization_controller.go index 1d4fb72..1deee80 100644 --- a/controllers/organization_controller.go +++ b/controllers/organization_controller.go @@ -167,7 +167,7 @@ func buildKeycloakGroup(org *orgv1.Organization, memb *controlv1.OrganizationMem groupMem = append(groupMem, u.Name) } - return keycloak.NewGroup(org.Name).WithMemberNames(groupMem...) + return keycloak.NewGroup(org.Spec.DisplayName, org.Name).WithMemberNames(groupMem...) } // SetupWithManager sets up the controller with the Manager. diff --git a/controllers/organization_controller_test.go b/controllers/organization_controller_test.go index 28355ac..69dc8fb 100644 --- a/controllers/organization_controller_test.go +++ b/controllers/organization_controller_test.go @@ -28,7 +28,7 @@ func Test_OrganizationController_Reconcile_Success(t *testing.T) { ctx := context.Background() c, keyMock, _ := prepareTest(t, fooOrg, fooMemb) - group := keycloak.NewGroup("foo").WithMemberNames("bar", "bar3") + group := keycloak.NewGroup("Foo Inc.", "foo").WithMemberNames("bar", "bar3") keyMock.EXPECT(). PutGroup(gomock.Any(), group). Return(group, nil). @@ -59,7 +59,7 @@ func Test_OrganizationController_Reconcile_Failure(t *testing.T) { ctx := context.Background() c, keyMock, erMock := prepareTest(t, fooOrg, fooMemb) - group := keycloak.NewGroup("foo").WithMemberNames("bar", "bar3") + group := keycloak.NewGroup("Foo Inc.", "foo").WithMemberNames("bar", "bar3") keyMock.EXPECT(). PutGroup(gomock.Any(), group). Return(keycloak.Group{}, errors.New("create failed")). @@ -95,7 +95,7 @@ func Test_OrganizationController_Reconcile_Member_Failure(t *testing.T) { ctx := context.Background() c, keyMock, erMock := prepareTest(t, fooOrg, fooMemb) - group := keycloak.NewGroup("foo").WithMemberNames("bar", "bar3") + group := keycloak.NewGroup("Foo Inc.", "foo").WithMemberNames("bar", "bar3") keyMock.EXPECT(). PutGroup(gomock.Any(), group). Return(keycloak.Group{}, &keycloak.MembershipSyncErrors{ diff --git a/controllers/periodic_syncer_test.go b/controllers/periodic_syncer_test.go index ee6569d..8e166f8 100644 --- a/controllers/periodic_syncer_test.go +++ b/controllers/periodic_syncer_test.go @@ -45,12 +45,12 @@ func Test_Sync_Success(t *testing.T) { }, ) - barOrg := keycloak.NewGroup("bar") + barOrg := keycloak.NewGroup("Bar Inc.", "bar") barOrg.Members = []keycloak.User{ {Username: "bar", DefaultOrganizationRef: "bar"}, {Username: "bar3", DefaultOrganizationRef: "bar-mss"}, } - barTeam := keycloak.NewGroup("bar", "bar-team") + barTeam := keycloak.NewGroup("Bar Team", "bar", "bar-team") barTeam.Members = []keycloak.User{ {Username: "bar-tm-1"}, {Username: "bar-tm-2", DefaultOrganizationRef: "bar-outsourcing"}, @@ -135,8 +135,8 @@ func Test_Sync_Fail_Update(t *testing.T) { // By not adding buzzMember manually we simulate an error while updating the members resource groups := []keycloak.Group{ - keycloak.NewGroup("buzz").WithMemberNames("buzz1", "buzz"), - keycloak.NewGroup("bar").WithMemberNames("bar", "bar3"), + keycloak.NewGroup("Buzz Inc.", "buzz").WithMemberNames("buzz1", "buzz"), + keycloak.NewGroup("Bar Inc.", "bar").WithMemberNames("bar", "bar3"), } keyMock.EXPECT(). ListGroups(gomock.Any()). @@ -175,8 +175,8 @@ func Test_Sync_Skip_Existing(t *testing.T) { c, keyMock, _ := prepareTest(t, fooOrg, fooMemb, barTeam) // We need to add barMember manually as there is no control API in the tests creating them groups := []keycloak.Group{ - keycloak.NewGroup("foo").WithMemberNames("foo", "foo2"), - keycloak.NewGroup("foo", "bar").WithMemberNames("updated-member-1", "updated-member-2"), + keycloak.NewGroup("Foo Inc.", "foo").WithMemberNames("foo", "foo2"), + keycloak.NewGroup("Foo Inc. Bar Team", "foo", "bar").WithMemberNames("updated-member-1", "updated-member-2"), } keyMock.EXPECT(). ListGroups(gomock.Any()). @@ -226,7 +226,7 @@ func Test_Sync_Skip_ExistingUsers(t *testing.T) { c, keyMock, _ := prepareTest(t, fooOrg, fooMemb, &subject) - fooGroup := keycloak.NewGroup("foo") + fooGroup := keycloak.NewGroup("Foo Inc.", "foo") fooGroup.Members = []keycloak.User{ { Username: subject.Name, @@ -257,8 +257,8 @@ func Test_Sync_Skip_UserInMultipleGroups(t *testing.T) { keyMock.EXPECT(). ListGroups(gomock.Any()). Return([]keycloak.Group{ - keycloak.NewGroup("foo").WithMemberNames("in-multiple-groups"), - keycloak.NewGroup("foo", "bar").WithMemberNames("in-multiple-groups"), + keycloak.NewGroup("Foo Inc.", "foo").WithMemberNames("in-multiple-groups"), + keycloak.NewGroup("Foo Inc. Bar Team", "foo", "bar").WithMemberNames("in-multiple-groups"), }, nil). Times(1) diff --git a/keycloak/ZZ_mock_gocloak_test.go b/keycloak/ZZ_mock_gocloak_test.go index f7d6af4..c74dfd6 100644 --- a/keycloak/ZZ_mock_gocloak_test.go +++ b/keycloak/ZZ_mock_gocloak_test.go @@ -181,6 +181,20 @@ func (mr *MockGoCloakMockRecorder) LogoutPublicClient(ctx, clientID, realm, acce return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogoutPublicClient", reflect.TypeOf((*MockGoCloak)(nil).LogoutPublicClient), ctx, clientID, realm, accessToken, refreshToken) } +// UpdateGroup mocks base method. +func (m *MockGoCloak) UpdateGroup(ctx context.Context, accessToken, realm string, updatedGroup gocloak.Group) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateGroup", ctx, accessToken, realm, updatedGroup) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateGroup indicates an expected call of UpdateGroup. +func (mr *MockGoCloakMockRecorder) UpdateGroup(ctx, accessToken, realm, updatedGroup interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGroup", reflect.TypeOf((*MockGoCloak)(nil).UpdateGroup), ctx, accessToken, realm, updatedGroup) +} + // UpdateUser mocks base method. func (m *MockGoCloak) UpdateUser(ctx context.Context, accessToken, realm string, user gocloak.User) error { m.ctrl.T.Helper() diff --git a/keycloak/client.go b/keycloak/client.go index dcd774c..64d7be0 100644 --- a/keycloak/client.go +++ b/keycloak/client.go @@ -15,16 +15,18 @@ type Group struct { path []string Members []User + + displayName string } // NewGroup creates a new group. -func NewGroup(path ...string) Group { - return Group{path: path} +func NewGroup(displayName string, path ...string) Group { + return Group{path: path, displayName: displayName} } // NewGroupFromPath creates a new group. -func NewGroupFromPath(path string) Group { - return NewGroup(strings.Split(strings.TrimPrefix(path, "/"), "/")...) +func NewGroupFromPath(displayName string, path string) Group { + return NewGroup(displayName, strings.Split(strings.TrimPrefix(path, "/"), "/")...) } // WithMemberNames returns a copy of the group with given members added. @@ -119,6 +121,7 @@ type GoCloak interface { CreateGroup(ctx context.Context, accessToken, realm string, group gocloak.Group) (string, error) CreateChildGroup(ctx context.Context, accessToken, realm, groupID string, group gocloak.Group) (string, error) GetGroups(ctx context.Context, accessToken, realm string, params gocloak.GetGroupsParams) ([]*gocloak.Group, error) + UpdateGroup(ctx context.Context, accessToken, realm string, updatedGroup gocloak.Group) error DeleteGroup(ctx context.Context, accessToken, realm, groupID string) error GetGroupMembers(ctx context.Context, accessToken, realm, groupID string, params gocloak.GetGroupsParams) ([]*gocloak.User, error) @@ -157,7 +160,7 @@ func NewClient(host, realm, username, password string) Client { // PutGroup creates the provided Keycloak group if it does not exist and adjusts the group members accordingly. // The method is idempotent. func (c Client) PutGroup(ctx context.Context, group Group) (Group, error) { - res := NewGroup(group.path...) + res := NewGroup(group.displayName, group.path...) group = c.prependRoot(group) token, err := c.login(ctx) @@ -176,6 +179,14 @@ func (c Client) PutGroup(ctx context.Context, group Group) (Group, error) { return res, err } found = &created + } else { + if getDisplayNameOfGroup(found) != group.displayName { + found.Attributes = setDisplayName(found.Attributes, group.displayName) + err := c.updateGroup(ctx, token, *found) + if err != nil { + return res, err + } + } } membErr := MembershipSyncErrors{} @@ -212,8 +223,9 @@ func (c Client) PutGroup(ctx context.Context, group Group) (Group, error) { func (c Client) createGroup(ctx context.Context, token *gocloak.JWT, group Group) (gocloak.Group, error) { toCreate := gocloak.Group{ - Name: gocloak.StringP(group.BaseName()), - Path: gocloak.StringP(group.Path()), + Name: gocloak.StringP(group.BaseName()), + Path: gocloak.StringP(group.Path()), + Attributes: setDisplayName(nil, group.displayName), } if len(group.PathMembers()) == 1 { @@ -223,7 +235,7 @@ func (c Client) createGroup(ctx context.Context, token *gocloak.JWT, group Group } p := group.PathMembers() - parent, err := c.getGroup(ctx, token, NewGroup(p[0:len(p)-1]...)) + parent, err := c.getGroup(ctx, token, NewGroup(group.displayName, p[0:len(p)-1]...)) if err != nil { return toCreate, fmt.Errorf("error finding parent group for %v: %w", group, err) } @@ -236,6 +248,11 @@ func (c Client) createGroup(ctx context.Context, token *gocloak.JWT, group Group return toCreate, err } +func (c Client) updateGroup(ctx context.Context, token *gocloak.JWT, group gocloak.Group) error { + err := c.Client.UpdateGroup(ctx, token.AccessToken, c.Realm, group) + return err +} + // DeleteGroup deletes the Keycloak group by name. // The method is idempotent and will not do anything if the group does not exits. func (c Client) DeleteGroup(ctx context.Context, path ...string) error { @@ -245,7 +262,7 @@ func (c Client) DeleteGroup(ctx context.Context, path ...string) error { } defer c.logout(ctx, token) - found, err := c.getGroup(ctx, token, c.prependRoot(NewGroup(path...))) + found, err := c.getGroup(ctx, token, c.prependRoot(NewGroup("", path...))) if err != nil { return fmt.Errorf("failed finding group: %w", err) } @@ -488,7 +505,7 @@ func flatGroups(gcp []gocloak.Group) []Group { var flatten func([]gocloak.Group) flatten = func(groups []gocloak.Group) { for _, g := range groups { - group := NewGroupFromPath(*g.Path) + group := NewGroupFromPath(getDisplayNameOfGroup(&g), *g.Path) group.id = *g.ID flat = append(flat, group) if g.SubGroups != nil { @@ -501,6 +518,30 @@ func flatGroups(gcp []gocloak.Group) []Group { return flat } +func getDisplayNameOfGroup(group *gocloak.Group) string { + if group.Attributes != nil { + displayNames, ok := (*group.Attributes)["displayName"] + if ok && len(displayNames) > 0 { + return displayNames[0] + } + } + return "" +} + +func setDisplayName(attributes *map[string][]string, displayName string) *map[string][]string { + if attributes == nil { + attrMap := make(map[string][]string) + attributes = &attrMap + } + if displayName == "" { + delete(*attributes, "displayName") + } else { + (*attributes)["displayName"] = []string{displayName} + } + return attributes +} + var defaultParams = gocloak.GetGroupsParams{ - Max: gocloak.IntP(-1), + Max: gocloak.IntP(-1), + BriefRepresentation: gocloak.BoolP(false), // required in order to get attributes when listing groups } diff --git a/keycloak/client_delete_test.go b/keycloak/client_delete_test.go index 0631e87..e4b14c5 100644 --- a/keycloak/client_delete_test.go +++ b/keycloak/client_delete_test.go @@ -27,7 +27,7 @@ func TestDeleteGroup_simple(t *testing.T) { mockLogin(mKeycloak, c) mockGetGroups(mKeycloak, c, "foo-gmbh", []*gocloak.Group{ - newGocloakGroup("foo-id", "foo-gmbh"), + newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"), }) mockDeleteGroup(mKeycloak, c, "foo-id") @@ -47,7 +47,7 @@ func TestDeleteGroup_RootGroup(t *testing.T) { mockLogin(mKeycloak, c) mockGetGroups(mKeycloak, c, "foo-gmbh", []*gocloak.Group{ - newGocloakGroup("foo-id", "root-group", "foo-gmbh"), + newGocloakGroup("Foo Inc.", "foo-id", "root-group", "foo-gmbh"), }) mockDeleteGroup(mKeycloak, c, "foo-id") @@ -69,7 +69,7 @@ func TestDeleteGroup_subgroup(t *testing.T) { mockLogin(mKeycloak, c) mockGetGroups(mKeycloak, c, "foo-gmbh", []*gocloak.Group{ - newGocloakGroup("foo-id", "parent", "foo-gmbh"), + newGocloakGroup("Foo Inc.", "foo-id", "parent", "foo-gmbh"), }) mockDeleteGroup(mKeycloak, c, "foo-id") diff --git a/keycloak/client_list_test.go b/keycloak/client_list_test.go index 9b77c3a..a60c117 100644 --- a/keycloak/client_list_test.go +++ b/keycloak/client_list_test.go @@ -25,11 +25,11 @@ func TestListGroups_simple(t *testing.T) { } gs := []*gocloak.Group{ - newGocloakGroup("foo-id", "foo-gmbh"), - newGocloakGroup("bar-id", "bar-gmbh"), + newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"), + newGocloakGroup("Bar Inc.", "bar-id", "bar-gmbh"), func() *gocloak.Group { - g := newGocloakGroup("parent-id", "parent-gmbh") - g.SubGroups = &[]gocloak.Group{*newGocloakGroup("qux-id", "parent-gmbh", "qux-team")} + g := newGocloakGroup("", "parent-id", "parent-gmbh") + g.SubGroups = &[]gocloak.Group{*newGocloakGroup("Parent GmbH", "qux-id", "parent-gmbh", "qux-team")} return g }(), } @@ -75,13 +75,13 @@ func TestListGroups_RootGroup(t *testing.T) { } gs := []*gocloak.Group{ - newGocloakGroup("foo-id", "foo-gmbh"), + newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"), func() *gocloak.Group { - g := newGocloakGroup("root-group-id", "root-group") + g := newGocloakGroup("", "root-group-id", "root-group") g.SubGroups = &[]gocloak.Group{ func() gocloak.Group { - g := *newGocloakGroup("foo-gmbh-id", "root-group", "foo-gmbh") - g.SubGroups = &[]gocloak.Group{*newGocloakGroup("foo-team-id", "root-group", "foo-gmbh", "foo-team")} + g := *newGocloakGroup("Foo Inc.", "foo-gmbh-id", "root-group", "foo-gmbh") + g.SubGroups = &[]gocloak.Group{*newGocloakGroup("Foo Team", "foo-team-id", "root-group", "foo-gmbh", "foo-team")} return g }()} return g @@ -112,8 +112,8 @@ func TestListGroups_RootGroup_no_groups_under_root(t *testing.T) { } gs := []*gocloak.Group{ - newGocloakGroup("foo-id", "foo-gmbh"), - newGocloakGroup("root-group-id", "root-group"), + newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"), + newGocloakGroup("", "root-group-id", "root-group"), } mockLogin(mKeycloak, c) mockListGroups(mKeycloak, c, gs) @@ -134,7 +134,7 @@ func TestListGroups_RootGroup_RootNotFound(t *testing.T) { } gs := []*gocloak.Group{ - newGocloakGroup("foo-id", "foo-gmbh"), + newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"), } mockLogin(mKeycloak, c) mockListGroups(mKeycloak, c, gs) diff --git a/keycloak/client_put_test.go b/keycloak/client_put_test.go index f893798..1146dcf 100644 --- a/keycloak/client_put_test.go +++ b/keycloak/client_put_test.go @@ -28,7 +28,7 @@ func TestPutGroup_simple(t *testing.T) { mockLogin(mKeycloak, c) mockGetGroups(mKeycloak, c, "foo-gmbh", []*gocloak.Group{ - newGocloakGroup("foo-id", "foo-gmbh"), + newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"), }) mockGetGroupMembers(mKeycloak, c, "foo-id", []*gocloak.User{ @@ -42,7 +42,7 @@ func TestPutGroup_simple(t *testing.T) { mockAddUser(mKeycloak, c, "3", "foo-id") mockAddUser(mKeycloak, c, "2", "foo-id") - g, err := c.PutGroup(context.TODO(), NewGroup("foo-gmbh").WithMemberNames("user", "user2", "user3")) + g, err := c.PutGroup(context.TODO(), NewGroup("Foo Inc.", "foo-gmbh").WithMemberNames("user", "user2", "user3")) require.NoError(t, err) assert.Len(t, g.Members, 3) } @@ -60,8 +60,8 @@ func TestPutGroup_RootGroup_update(t *testing.T) { mockGetGroups(mKeycloak, c, "foo-gmbh", []*gocloak.Group{ func() *gocloak.Group { - g := newGocloakGroup("root-group-id", "root-group") - g.SubGroups = &[]gocloak.Group{*newGocloakGroup("foo-id", "root-group", "foo-gmbh")} + g := newGocloakGroup("Foo Inc.", "root-group-id", "root-group") + g.SubGroups = &[]gocloak.Group{*newGocloakGroup("Foo Inc.", "foo-id", "root-group", "foo-gmbh")} return g }(), }) @@ -77,7 +77,7 @@ func TestPutGroup_RootGroup_update(t *testing.T) { mockAddUser(mKeycloak, c, "3", "foo-id") mockAddUser(mKeycloak, c, "2", "foo-id") - g, err := c.PutGroup(context.TODO(), NewGroup("foo-gmbh").WithMemberNames("user", "user2", "user3")) + g, err := c.PutGroup(context.TODO(), NewGroup("Foo Inc.", "foo-gmbh").WithMemberNames("user", "user2", "user3")) require.NoError(t, err) assert.Len(t, g.Members, 3) } @@ -95,11 +95,11 @@ func TestPutGroup_new(t *testing.T) { } mockLogin(mKeycloak, c) mockGetGroups(mKeycloak, c, "foo-gmbh", []*gocloak.Group{}) - mockCreateGroup(mKeycloak, c, "foo-gmbh", "/foo-gmbh", "foo-id") + mockCreateGroup(mKeycloak, c, "foo-gmbh", "Foo Inc.", "/foo-gmbh", "foo-id") mockGetUser(mKeycloak, c, "user", "1") mockAddUser(mKeycloak, c, "1", "foo-id") - g, err := c.PutGroup(context.TODO(), NewGroup("foo-gmbh").WithMemberNames("user")) + g, err := c.PutGroup(context.TODO(), NewGroup("Foo Inc.", "foo-gmbh").WithMemberNames("user")) require.NoError(t, err) require.Equal(t, "/foo-gmbh", g.Path()) assert.Len(t, g.Members, 1) @@ -123,11 +123,11 @@ func TestPutGroup_RootGroup_new(t *testing.T) { Name: gocloak.StringP("root-group"), }, }) - mockCreateChildGroup(mKeycloak, c, "root-group-id", "foo-gmbh", "/root-group/foo-gmbh", "foo-id") + mockCreateChildGroup(mKeycloak, c, "root-group-id", "foo-gmbh", "Foo Inc.", "/root-group/foo-gmbh", "foo-id") mockGetUser(mKeycloak, c, "user", "1") mockAddUser(mKeycloak, c, "1", "foo-id") - g, err := c.PutGroup(context.TODO(), NewGroup("foo-gmbh").WithMemberNames("user")) + g, err := c.PutGroup(context.TODO(), NewGroup("Foo Inc.", "foo-gmbh").WithMemberNames("user")) require.NoError(t, err) require.Equal(t, "/foo-gmbh", g.Path()) assert.Len(t, g.Members, 1) @@ -146,7 +146,7 @@ func TestPutGroup_RootGroup_non_existing_root_group(t *testing.T) { mockGetGroups(mKeycloak, c, "foo-gmbh", []*gocloak.Group{}) mockGetGroups(mKeycloak, c, "root-group", []*gocloak.Group{}) - _, err := c.PutGroup(context.TODO(), NewGroup("foo-gmbh").WithMemberNames("user")) + _, err := c.PutGroup(context.TODO(), NewGroup("Foo Inc.", "foo-gmbh").WithMemberNames("user")) require.Error(t, err) } @@ -170,11 +170,11 @@ func TestPutGroup_new_with_path(t *testing.T) { Name: gocloak.StringP("Parent"), }, }) - mockCreateChildGroup(mKeycloak, c, "Parent-ID", "foo-gmbh", "/Parent/foo-gmbh", "foo-id") + mockCreateChildGroup(mKeycloak, c, "Parent-ID", "foo-gmbh", "Foo Inc.", "/Parent/foo-gmbh", "foo-id") mockGetUser(mKeycloak, c, "user", "1") mockAddUser(mKeycloak, c, "1", "foo-id") - g, err := c.PutGroup(context.TODO(), NewGroup("Parent", "foo-gmbh").WithMemberNames("user")) + g, err := c.PutGroup(context.TODO(), NewGroup("Foo Inc.", "Parent", "foo-gmbh").WithMemberNames("user")) require.NoError(t, err) require.Equal(t, "/Parent/foo-gmbh", g.Path()) assert.Len(t, g.Members, 1) @@ -194,8 +194,8 @@ func TestPutGroup_multiple_matching_groups(t *testing.T) { mockLogin(mKeycloak, c) mockGetGroups(mKeycloak, c, "foo-gmbh", []*gocloak.Group{ - newGocloakGroup("test-id", "foo-gmbh-test"), - newGocloakGroup("foo-id", "foo-gmbh"), + newGocloakGroup("Foo Inc.", "test-id", "foo-gmbh-test"), + newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"), }) mockGetGroupMembers(mKeycloak, c, "foo-id", []*gocloak.User{ @@ -209,7 +209,7 @@ func TestPutGroup_multiple_matching_groups(t *testing.T) { mockAddUser(mKeycloak, c, "3", "foo-id") mockAddUser(mKeycloak, c, "2", "foo-id") - g, err := c.PutGroup(context.TODO(), NewGroup("foo-gmbh").WithMemberNames("user", "user2", "user3")) + g, err := c.PutGroup(context.TODO(), NewGroup("Foo Inc.", "foo-gmbh").WithMemberNames("user", "user2", "user3")) require.NoError(t, err) assert.Len(t, g.Members, 3) } @@ -227,7 +227,7 @@ func TestPutGroup_multiple_matching_users(t *testing.T) { mockLogin(mKeycloak, c) mockGetGroups(mKeycloak, c, "foo-gmbh", []*gocloak.Group{ - newGocloakGroup("foo-id", "foo-gmbh"), + newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"), }) mockGetGroupMembers(mKeycloak, c, "foo-id", []*gocloak.User{}) mockGetUsers(mKeycloak, c, "user", []*gocloak.User{ @@ -236,7 +236,7 @@ func TestPutGroup_multiple_matching_users(t *testing.T) { }) mockAddUser(mKeycloak, c, "1", "foo-id") - g, err := c.PutGroup(context.TODO(), NewGroup("foo-gmbh").WithMemberNames("user")) + g, err := c.PutGroup(context.TODO(), NewGroup("Foo Inc.", "foo-gmbh").WithMemberNames("user")) require.NoError(t, err) assert.Len(t, g.Members, 1) } @@ -255,7 +255,7 @@ func TestPutGroup_remove_member(t *testing.T) { mockLogin(mKeycloak, c) mockGetGroups(mKeycloak, c, "foo-gmbh", []*gocloak.Group{ - newGocloakGroup("foo-id", "foo-gmbh"), + newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"), }) mockGetGroupMembers(mKeycloak, c, "foo-id", []*gocloak.User{ @@ -274,7 +274,7 @@ func TestPutGroup_remove_member(t *testing.T) { mockAddUser(mKeycloak, c, "2", "foo-id") mockRemoveUser(mKeycloak, c, "4", "foo-id") - g, err := c.PutGroup(context.TODO(), NewGroup("foo-gmbh").WithMemberNames("user", "user2", "user3")) + g, err := c.PutGroup(context.TODO(), NewGroup("Foo Inc.", "foo-gmbh").WithMemberNames("user", "user2", "user3")) require.NoError(t, err) assert.Len(t, g.Members, 3) } diff --git a/keycloak/suite_test.go b/keycloak/suite_test.go index 8049c37..dbddccc 100644 --- a/keycloak/suite_test.go +++ b/keycloak/suite_test.go @@ -27,7 +27,8 @@ func mockLogin(mgc *MockGoCloak, c Client) { func mockListGroups(mgc *MockGoCloak, c Client, groups []*gocloak.Group) { mgc.EXPECT(). GetGroups(gomock.Any(), "token", c.Realm, gocloak.GetGroupsParams{ - Max: gocloak.IntP(-1), + Max: gocloak.IntP(-1), + BriefRepresentation: gocloak.BoolP(false), }). Return(groups, nil). Times(1) @@ -43,20 +44,34 @@ func mockGetGroups(mgc *MockGoCloak, c Client, groupName string, groups []*goclo Times(1) } -func mockCreateGroup(mgc *MockGoCloak, c Client, groupName, groupPath, groupID string) { +func mockCreateGroup(mgc *MockGoCloak, c Client, groupName, groupDisplayName, groupPath, groupID string) { + var attributes *map[string][]string + if groupDisplayName != "" { + attrMap := make(map[string][]string) + attrMap["displayName"] = []string{groupDisplayName} + attributes = &attrMap + } kcg := gocloak.Group{ - Name: &groupName, - Path: &groupPath, + Name: &groupName, + Path: &groupPath, + Attributes: attributes, } mgc.EXPECT(). CreateGroup(gomock.Any(), "token", c.Realm, kcg). Return(groupID, nil). Times(1) } -func mockCreateChildGroup(mgc *MockGoCloak, c Client, parentID, groupName, groupPath, groupID string) { +func mockCreateChildGroup(mgc *MockGoCloak, c Client, parentID, groupName, groupDisplayName, groupPath, groupID string) { + var attributes *map[string][]string + if groupDisplayName != "" { + attrMap := make(map[string][]string) + attrMap["displayName"] = []string{groupDisplayName} + attributes = &attrMap + } kcg := gocloak.Group{ - Name: &groupName, - Path: &groupPath, + Name: &groupName, + Path: &groupPath, + Attributes: attributes, } mgc.EXPECT(). CreateChildGroup(gomock.Any(), "token", c.Realm, parentID, kcg). @@ -116,13 +131,20 @@ func mockUpdateUser(mgc *MockGoCloak, c Client, user gocloak.User) { Times(1) } -func newGocloakGroup(id string, path ...string) *gocloak.Group { +func newGocloakGroup(displayName string, id string, path ...string) *gocloak.Group { if len(path) == 0 { panic("group must have at least one element in path") } + var attributes *map[string][]string + if displayName != "" { + attrMap := make(map[string][]string) + attrMap["displayName"] = []string{displayName} + attributes = &attrMap + } return &gocloak.Group{ - ID: &id, - Name: gocloak.StringP(path[len(path)-1]), - Path: gocloak.StringP("/" + strings.Join(path, "/")), + ID: &id, + Name: gocloak.StringP(path[len(path)-1]), + Path: gocloak.StringP("/" + strings.Join(path, "/")), + Attributes: attributes, } }