|
| 1 | +```go |
| 2 | +type Tkc struct { |
| 3 | + realm string |
| 4 | + token *gocloak.JWT |
| 5 | + client *gocloak.GoCloak |
| 6 | +} |
| 7 | + |
| 8 | +func (tkc *Tkc) getRealms(ctx context.Context) (realms map[string]gocloak.RealmRepresentation, getErr error) { |
| 9 | + var realmList []*gocloak.RealmRepresentation |
| 10 | + |
| 11 | + // init map |
| 12 | + realms = make(map[string]gocloak.RealmRepresentation) |
| 13 | + |
| 14 | + // get all realms |
| 15 | + if realmList, getErr = tkc.client.GetRealms(ctx, tkc.token.AccessToken); getErr != nil { |
| 16 | + getErr = fmt.Errorf("get realms failed (error: %s)", getErr.Error()) |
| 17 | + return realms, getErr |
| 18 | + } |
| 19 | + |
| 20 | + // transform to map with realmID (meaning realm name) as map key |
| 21 | + for _, r := range realmList { |
| 22 | + realms[*r.Realm] = *r |
| 23 | + } |
| 24 | + |
| 25 | + return realms, nil |
| 26 | +} |
| 27 | + |
| 28 | +func (tkc *Tkc) newUserFederation(ctx context.Context) (userFederation gocloak.Component, newErr error) { |
| 29 | + var idRealm string |
| 30 | + |
| 31 | + // get realm ID, is needed as ParentID in gocloak.Component |
| 32 | + if realms, newErr := tkc.getRealms(ctx); newErr != nil { |
| 33 | + return userFederation, newErr |
| 34 | + } else { |
| 35 | + idRealm = *realms[tkc.realm].ID |
| 36 | + } |
| 37 | + |
| 38 | + userFederationConfig := make(map[string][]string) |
| 39 | + |
| 40 | + // keycloak self |
| 41 | + userFederationConfig["enabled"] = []string{"true"} |
| 42 | + userFederationConfig["priority"] = []string{"1"} |
| 43 | + userFederationConfig["importUsers"] = []string{"true"} |
| 44 | + |
| 45 | + // sync options |
| 46 | + userFederationConfig["fullSyncPeriod"] = []string{"-1"} |
| 47 | + userFederationConfig["changedSyncPeriod"] = []string{"300"} |
| 48 | + userFederationConfig["batchSizeForSync"] = []string{"1000"} |
| 49 | + |
| 50 | + // ldap connection |
| 51 | + userFederationConfig["editMode"] = []string{"READ_ONLY"} |
| 52 | + userFederationConfig["vendor"] = []string{"other"} |
| 53 | + userFederationConfig["connectionUrl"] = []string{"ldap://ldap"} |
| 54 | + userFederationConfig["bindDn"] = []string{"cn=XXX,dc=example,dc=com"} |
| 55 | + userFederationConfig["bindCredential"] = []string{"YYYYYY"} |
| 56 | + userFederationConfig["usersDn"] = []string{"ou=users,dc=example,dc=com"} |
| 57 | + userFederationConfig["usernameLDAPAttribute"] = []string{"uid"} |
| 58 | + userFederationConfig["uuidLDAPAttribute"] = []string{"entryUUID"} |
| 59 | + userFederationConfig["authType"] = []string{"simple"} |
| 60 | + userFederationConfig["userObjectClasses"] = []string{"person, uidObject"} |
| 61 | + userFederationConfig["rdnLDAPAttribute"] = []string{"cn"} |
| 62 | + userFederationConfig["searchScope"] = []string{"1"} |
| 63 | + userFederationConfig["pagination"] = []string{"true"} |
| 64 | + |
| 65 | + userFederation = gocloak.Component{ |
| 66 | + Name: gocloak.StringP("ldap"), |
| 67 | + ProviderID: gocloak.StringP("ldap"), |
| 68 | + ProviderType: gocloak.StringP("org.keycloak.storage.UserStorageProvider"), |
| 69 | + ParentID: gocloak.StringP(idRealm), |
| 70 | + ComponentConfig: &userFederationConfig, |
| 71 | + } |
| 72 | + |
| 73 | + return userFederation, nil |
| 74 | +} |
| 75 | + |
| 76 | +func (tkc *Tkc) RunCreateUserFederation() error { |
| 77 | + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
| 78 | + |
| 79 | + defer func() { |
| 80 | + cancel() |
| 81 | + }() |
| 82 | + |
| 83 | + // user federation is a keycloak component with type 'org.keycloak.storage.UserStorageProvider' |
| 84 | + var userFederation gocloak.Component |
| 85 | + |
| 86 | + // get default new legacy user federation to create or update an existing one |
| 87 | + if newUserFederation, runErr := tkc.newUserFederation(ctx); runErr != nil { |
| 88 | + return runErr |
| 89 | + } else { |
| 90 | + userFederation = newUserFederation |
| 91 | + } |
| 92 | + |
| 93 | + // search parameter to get exactly legacy user federation which belongs to given realm (parent) |
| 94 | + ldapGetParams := gocloak.GetComponentsParams{ |
| 95 | + Name: userFederation.Name, |
| 96 | + ProviderType: userFederation.ProviderType, |
| 97 | + ParentID: userFederation.ParentID, |
| 98 | + } |
| 99 | + |
| 100 | + if comps, getErr := tkc.client.GetComponentsWithParams(ctx, tkc.token.AccessToken, tkc.realm, ldapGetParams); getErr != nil { |
| 101 | + return getErr |
| 102 | + } else { |
| 103 | + // means user federation not found for given realm |
| 104 | + if len(comps) == 0 { |
| 105 | + if idUserFederation, createErr := tkc.client.CreateComponent(ctx, tkc.token.AccessToken, tkc.realm, userFederation); createErr != nil { |
| 106 | + return createErr |
| 107 | + } else { |
| 108 | + // do full sync after creating new one |
| 109 | + if syncErr := tkc.syncUserFederation(ctx, idUserFederation, true); syncErr != nil { |
| 110 | + syncErr = fmt.Errorf("legacy user federation '%s' created (%s), but sync failed", *userFederation.Name, idUserFederation) |
| 111 | + return syncErr |
| 112 | + } |
| 113 | + } |
| 114 | + } else { |
| 115 | + // set ID of user federation for update exactly existing user federation |
| 116 | + userFederation.ID = comps[0].ID |
| 117 | + if updateErr := tkc.client.UpdateComponent(ctx, tkc.token.AccessToken, tkc.baseConfig.Realm, userFederation); updateErr != nil { |
| 118 | + return updateErr |
| 119 | + } else { |
| 120 | + // do change sync only after update exiting one |
| 121 | + if syncErr := tkc.syncUserFederation(ctx, *userFederation.ID, false); syncErr != nil { |
| 122 | + syncErr = fmt.Errorf("legacy user federation '%s' updated (%s), but sync failed", *userFederation.Name, *userFederation.ID) |
| 123 | + return syncErr |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + return nil |
| 130 | +} |
| 131 | + |
| 132 | +func (tkc *Tkc) syncUserFederation(ctx context.Context, idUserFederation string, fullSync bool) error { |
| 133 | + var url string |
| 134 | + |
| 135 | + url = tkc.baseConfig.Url + "/admin/realms/" + tkc.realm + "/user-storage/" + idUserFederation + "/sync" |
| 136 | + |
| 137 | + if fullSync { |
| 138 | + url += "?action=triggerFullSync" |
| 139 | + } else { |
| 140 | + url += "?action=triggerChangedUsersSync" |
| 141 | + } |
| 142 | + |
| 143 | + if response, postErr := tkc.client.RestyClient().NewRequest().SetAuthToken(tkc.token.AccessToken).Post(url); postErr != nil { |
| 144 | + return postErr |
| 145 | + } else { |
| 146 | + if response.StatusCode() != 200 { |
| 147 | + postErr = fmt.Errorf("got status code '%d' with response body '%s'", response.StatusCode(), response.String()) |
| 148 | + return postErr |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + return nil |
| 153 | +} |
| 154 | +} |
| 155 | +``` |
0 commit comments