Skip to content

Commit 81537bb

Browse files
authored
Merge pull request #57 from vshn/display-name-sync
feat: Sync display name to Keycloak
2 parents 84de744 + 85d14bb commit 81537bb

9 files changed

+145
-68
lines changed

controllers/organization_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ func buildKeycloakGroup(org *orgv1.Organization, memb *controlv1.OrganizationMem
167167
groupMem = append(groupMem, u.Name)
168168
}
169169

170-
return keycloak.NewGroup(org.Name).WithMemberNames(groupMem...)
170+
return keycloak.NewGroup(org.Spec.DisplayName, org.Name).WithMemberNames(groupMem...)
171171
}
172172

173173
// SetupWithManager sets up the controller with the Manager.

controllers/organization_controller_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func Test_OrganizationController_Reconcile_Success(t *testing.T) {
2828
ctx := context.Background()
2929

3030
c, keyMock, _ := prepareTest(t, fooOrg, fooMemb)
31-
group := keycloak.NewGroup("foo").WithMemberNames("bar", "bar3")
31+
group := keycloak.NewGroup("Foo Inc.", "foo").WithMemberNames("bar", "bar3")
3232
keyMock.EXPECT().
3333
PutGroup(gomock.Any(), group).
3434
Return(group, nil).
@@ -59,7 +59,7 @@ func Test_OrganizationController_Reconcile_Failure(t *testing.T) {
5959
ctx := context.Background()
6060

6161
c, keyMock, erMock := prepareTest(t, fooOrg, fooMemb)
62-
group := keycloak.NewGroup("foo").WithMemberNames("bar", "bar3")
62+
group := keycloak.NewGroup("Foo Inc.", "foo").WithMemberNames("bar", "bar3")
6363
keyMock.EXPECT().
6464
PutGroup(gomock.Any(), group).
6565
Return(keycloak.Group{}, errors.New("create failed")).
@@ -95,7 +95,7 @@ func Test_OrganizationController_Reconcile_Member_Failure(t *testing.T) {
9595
ctx := context.Background()
9696

9797
c, keyMock, erMock := prepareTest(t, fooOrg, fooMemb)
98-
group := keycloak.NewGroup("foo").WithMemberNames("bar", "bar3")
98+
group := keycloak.NewGroup("Foo Inc.", "foo").WithMemberNames("bar", "bar3")
9999
keyMock.EXPECT().
100100
PutGroup(gomock.Any(), group).
101101
Return(keycloak.Group{}, &keycloak.MembershipSyncErrors{

controllers/periodic_syncer_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ func Test_Sync_Success(t *testing.T) {
4545
},
4646
)
4747

48-
barOrg := keycloak.NewGroup("bar")
48+
barOrg := keycloak.NewGroup("Bar Inc.", "bar")
4949
barOrg.Members = []keycloak.User{
5050
{Username: "bar", DefaultOrganizationRef: "bar"},
5151
{Username: "bar3", DefaultOrganizationRef: "bar-mss"},
5252
}
53-
barTeam := keycloak.NewGroup("bar", "bar-team")
53+
barTeam := keycloak.NewGroup("Bar Team", "bar", "bar-team")
5454
barTeam.Members = []keycloak.User{
5555
{Username: "bar-tm-1"},
5656
{Username: "bar-tm-2", DefaultOrganizationRef: "bar-outsourcing"},
@@ -135,8 +135,8 @@ func Test_Sync_Fail_Update(t *testing.T) {
135135
// By not adding buzzMember manually we simulate an error while updating the members resource
136136

137137
groups := []keycloak.Group{
138-
keycloak.NewGroup("buzz").WithMemberNames("buzz1", "buzz"),
139-
keycloak.NewGroup("bar").WithMemberNames("bar", "bar3"),
138+
keycloak.NewGroup("Buzz Inc.", "buzz").WithMemberNames("buzz1", "buzz"),
139+
keycloak.NewGroup("Bar Inc.", "bar").WithMemberNames("bar", "bar3"),
140140
}
141141
keyMock.EXPECT().
142142
ListGroups(gomock.Any()).
@@ -175,8 +175,8 @@ func Test_Sync_Skip_Existing(t *testing.T) {
175175
c, keyMock, _ := prepareTest(t, fooOrg, fooMemb, barTeam) // We need to add barMember manually as there is no control API in the tests creating them
176176

177177
groups := []keycloak.Group{
178-
keycloak.NewGroup("foo").WithMemberNames("foo", "foo2"),
179-
keycloak.NewGroup("foo", "bar").WithMemberNames("updated-member-1", "updated-member-2"),
178+
keycloak.NewGroup("Foo Inc.", "foo").WithMemberNames("foo", "foo2"),
179+
keycloak.NewGroup("Foo Inc. Bar Team", "foo", "bar").WithMemberNames("updated-member-1", "updated-member-2"),
180180
}
181181
keyMock.EXPECT().
182182
ListGroups(gomock.Any()).
@@ -226,7 +226,7 @@ func Test_Sync_Skip_ExistingUsers(t *testing.T) {
226226

227227
c, keyMock, _ := prepareTest(t, fooOrg, fooMemb, &subject)
228228

229-
fooGroup := keycloak.NewGroup("foo")
229+
fooGroup := keycloak.NewGroup("Foo Inc.", "foo")
230230
fooGroup.Members = []keycloak.User{
231231
{
232232
Username: subject.Name,
@@ -257,8 +257,8 @@ func Test_Sync_Skip_UserInMultipleGroups(t *testing.T) {
257257
keyMock.EXPECT().
258258
ListGroups(gomock.Any()).
259259
Return([]keycloak.Group{
260-
keycloak.NewGroup("foo").WithMemberNames("in-multiple-groups"),
261-
keycloak.NewGroup("foo", "bar").WithMemberNames("in-multiple-groups"),
260+
keycloak.NewGroup("Foo Inc.", "foo").WithMemberNames("in-multiple-groups"),
261+
keycloak.NewGroup("Foo Inc. Bar Team", "foo", "bar").WithMemberNames("in-multiple-groups"),
262262
}, nil).
263263
Times(1)
264264

keycloak/ZZ_mock_gocloak_test.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

keycloak/client.go

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@ type Group struct {
1515
path []string
1616

1717
Members []User
18+
19+
displayName string
1820
}
1921

2022
// NewGroup creates a new group.
21-
func NewGroup(path ...string) Group {
22-
return Group{path: path}
23+
func NewGroup(displayName string, path ...string) Group {
24+
return Group{path: path, displayName: displayName}
2325
}
2426

2527
// NewGroupFromPath creates a new group.
26-
func NewGroupFromPath(path string) Group {
27-
return NewGroup(strings.Split(strings.TrimPrefix(path, "/"), "/")...)
28+
func NewGroupFromPath(displayName string, path string) Group {
29+
return NewGroup(displayName, strings.Split(strings.TrimPrefix(path, "/"), "/")...)
2830
}
2931

3032
// WithMemberNames returns a copy of the group with given members added.
@@ -119,6 +121,7 @@ type GoCloak interface {
119121
CreateGroup(ctx context.Context, accessToken, realm string, group gocloak.Group) (string, error)
120122
CreateChildGroup(ctx context.Context, accessToken, realm, groupID string, group gocloak.Group) (string, error)
121123
GetGroups(ctx context.Context, accessToken, realm string, params gocloak.GetGroupsParams) ([]*gocloak.Group, error)
124+
UpdateGroup(ctx context.Context, accessToken, realm string, updatedGroup gocloak.Group) error
122125
DeleteGroup(ctx context.Context, accessToken, realm, groupID string) error
123126

124127
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 {
157160
// PutGroup creates the provided Keycloak group if it does not exist and adjusts the group members accordingly.
158161
// The method is idempotent.
159162
func (c Client) PutGroup(ctx context.Context, group Group) (Group, error) {
160-
res := NewGroup(group.path...)
163+
res := NewGroup(group.displayName, group.path...)
161164
group = c.prependRoot(group)
162165

163166
token, err := c.login(ctx)
@@ -176,6 +179,14 @@ func (c Client) PutGroup(ctx context.Context, group Group) (Group, error) {
176179
return res, err
177180
}
178181
found = &created
182+
} else {
183+
if getDisplayNameOfGroup(found) != group.displayName {
184+
found.Attributes = setDisplayName(found.Attributes, group.displayName)
185+
err := c.updateGroup(ctx, token, *found)
186+
if err != nil {
187+
return res, err
188+
}
189+
}
179190
}
180191

181192
membErr := MembershipSyncErrors{}
@@ -212,8 +223,9 @@ func (c Client) PutGroup(ctx context.Context, group Group) (Group, error) {
212223

213224
func (c Client) createGroup(ctx context.Context, token *gocloak.JWT, group Group) (gocloak.Group, error) {
214225
toCreate := gocloak.Group{
215-
Name: gocloak.StringP(group.BaseName()),
216-
Path: gocloak.StringP(group.Path()),
226+
Name: gocloak.StringP(group.BaseName()),
227+
Path: gocloak.StringP(group.Path()),
228+
Attributes: setDisplayName(nil, group.displayName),
217229
}
218230

219231
if len(group.PathMembers()) == 1 {
@@ -223,7 +235,7 @@ func (c Client) createGroup(ctx context.Context, token *gocloak.JWT, group Group
223235
}
224236

225237
p := group.PathMembers()
226-
parent, err := c.getGroup(ctx, token, NewGroup(p[0:len(p)-1]...))
238+
parent, err := c.getGroup(ctx, token, NewGroup(group.displayName, p[0:len(p)-1]...))
227239
if err != nil {
228240
return toCreate, fmt.Errorf("error finding parent group for %v: %w", group, err)
229241
}
@@ -236,6 +248,11 @@ func (c Client) createGroup(ctx context.Context, token *gocloak.JWT, group Group
236248
return toCreate, err
237249
}
238250

251+
func (c Client) updateGroup(ctx context.Context, token *gocloak.JWT, group gocloak.Group) error {
252+
err := c.Client.UpdateGroup(ctx, token.AccessToken, c.Realm, group)
253+
return err
254+
}
255+
239256
// DeleteGroup deletes the Keycloak group by name.
240257
// The method is idempotent and will not do anything if the group does not exits.
241258
func (c Client) DeleteGroup(ctx context.Context, path ...string) error {
@@ -245,7 +262,7 @@ func (c Client) DeleteGroup(ctx context.Context, path ...string) error {
245262
}
246263
defer c.logout(ctx, token)
247264

248-
found, err := c.getGroup(ctx, token, c.prependRoot(NewGroup(path...)))
265+
found, err := c.getGroup(ctx, token, c.prependRoot(NewGroup("", path...)))
249266
if err != nil {
250267
return fmt.Errorf("failed finding group: %w", err)
251268
}
@@ -488,7 +505,7 @@ func flatGroups(gcp []gocloak.Group) []Group {
488505
var flatten func([]gocloak.Group)
489506
flatten = func(groups []gocloak.Group) {
490507
for _, g := range groups {
491-
group := NewGroupFromPath(*g.Path)
508+
group := NewGroupFromPath(getDisplayNameOfGroup(&g), *g.Path)
492509
group.id = *g.ID
493510
flat = append(flat, group)
494511
if g.SubGroups != nil {
@@ -501,6 +518,30 @@ func flatGroups(gcp []gocloak.Group) []Group {
501518
return flat
502519
}
503520

521+
func getDisplayNameOfGroup(group *gocloak.Group) string {
522+
if group.Attributes != nil {
523+
displayNames, ok := (*group.Attributes)["displayName"]
524+
if ok && len(displayNames) > 0 {
525+
return displayNames[0]
526+
}
527+
}
528+
return ""
529+
}
530+
531+
func setDisplayName(attributes *map[string][]string, displayName string) *map[string][]string {
532+
if attributes == nil {
533+
attrMap := make(map[string][]string)
534+
attributes = &attrMap
535+
}
536+
if displayName == "" {
537+
delete(*attributes, "displayName")
538+
} else {
539+
(*attributes)["displayName"] = []string{displayName}
540+
}
541+
return attributes
542+
}
543+
504544
var defaultParams = gocloak.GetGroupsParams{
505-
Max: gocloak.IntP(-1),
545+
Max: gocloak.IntP(-1),
546+
BriefRepresentation: gocloak.BoolP(false), // required in order to get attributes when listing groups
506547
}

keycloak/client_delete_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func TestDeleteGroup_simple(t *testing.T) {
2727
mockLogin(mKeycloak, c)
2828
mockGetGroups(mKeycloak, c, "foo-gmbh",
2929
[]*gocloak.Group{
30-
newGocloakGroup("foo-id", "foo-gmbh"),
30+
newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"),
3131
})
3232
mockDeleteGroup(mKeycloak, c, "foo-id")
3333

@@ -47,7 +47,7 @@ func TestDeleteGroup_RootGroup(t *testing.T) {
4747
mockLogin(mKeycloak, c)
4848
mockGetGroups(mKeycloak, c, "foo-gmbh",
4949
[]*gocloak.Group{
50-
newGocloakGroup("foo-id", "root-group", "foo-gmbh"),
50+
newGocloakGroup("Foo Inc.", "foo-id", "root-group", "foo-gmbh"),
5151
})
5252
mockDeleteGroup(mKeycloak, c, "foo-id")
5353

@@ -69,7 +69,7 @@ func TestDeleteGroup_subgroup(t *testing.T) {
6969
mockLogin(mKeycloak, c)
7070
mockGetGroups(mKeycloak, c, "foo-gmbh",
7171
[]*gocloak.Group{
72-
newGocloakGroup("foo-id", "parent", "foo-gmbh"),
72+
newGocloakGroup("Foo Inc.", "foo-id", "parent", "foo-gmbh"),
7373
})
7474
mockDeleteGroup(mKeycloak, c, "foo-id")
7575

keycloak/client_list_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ func TestListGroups_simple(t *testing.T) {
2525
}
2626

2727
gs := []*gocloak.Group{
28-
newGocloakGroup("foo-id", "foo-gmbh"),
29-
newGocloakGroup("bar-id", "bar-gmbh"),
28+
newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"),
29+
newGocloakGroup("Bar Inc.", "bar-id", "bar-gmbh"),
3030
func() *gocloak.Group {
31-
g := newGocloakGroup("parent-id", "parent-gmbh")
32-
g.SubGroups = &[]gocloak.Group{*newGocloakGroup("qux-id", "parent-gmbh", "qux-team")}
31+
g := newGocloakGroup("", "parent-id", "parent-gmbh")
32+
g.SubGroups = &[]gocloak.Group{*newGocloakGroup("Parent GmbH", "qux-id", "parent-gmbh", "qux-team")}
3333
return g
3434
}(),
3535
}
@@ -75,13 +75,13 @@ func TestListGroups_RootGroup(t *testing.T) {
7575
}
7676

7777
gs := []*gocloak.Group{
78-
newGocloakGroup("foo-id", "foo-gmbh"),
78+
newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"),
7979
func() *gocloak.Group {
80-
g := newGocloakGroup("root-group-id", "root-group")
80+
g := newGocloakGroup("", "root-group-id", "root-group")
8181
g.SubGroups = &[]gocloak.Group{
8282
func() gocloak.Group {
83-
g := *newGocloakGroup("foo-gmbh-id", "root-group", "foo-gmbh")
84-
g.SubGroups = &[]gocloak.Group{*newGocloakGroup("foo-team-id", "root-group", "foo-gmbh", "foo-team")}
83+
g := *newGocloakGroup("Foo Inc.", "foo-gmbh-id", "root-group", "foo-gmbh")
84+
g.SubGroups = &[]gocloak.Group{*newGocloakGroup("Foo Team", "foo-team-id", "root-group", "foo-gmbh", "foo-team")}
8585
return g
8686
}()}
8787
return g
@@ -112,8 +112,8 @@ func TestListGroups_RootGroup_no_groups_under_root(t *testing.T) {
112112
}
113113

114114
gs := []*gocloak.Group{
115-
newGocloakGroup("foo-id", "foo-gmbh"),
116-
newGocloakGroup("root-group-id", "root-group"),
115+
newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"),
116+
newGocloakGroup("", "root-group-id", "root-group"),
117117
}
118118
mockLogin(mKeycloak, c)
119119
mockListGroups(mKeycloak, c, gs)
@@ -134,7 +134,7 @@ func TestListGroups_RootGroup_RootNotFound(t *testing.T) {
134134
}
135135

136136
gs := []*gocloak.Group{
137-
newGocloakGroup("foo-id", "foo-gmbh"),
137+
newGocloakGroup("Foo Inc.", "foo-id", "foo-gmbh"),
138138
}
139139
mockLogin(mKeycloak, c)
140140
mockListGroups(mKeycloak, c, gs)

0 commit comments

Comments
 (0)