Skip to content

Latest commit

 

History

History
207 lines (177 loc) · 7.86 KB

USER_FEDERATION_GROUP_LDAP_MAPPER.md

File metadata and controls

207 lines (177 loc) · 7.86 KB
type Tkc struct {
	realm  string
	token  *gocloak.JWT
	client *gocloak.GoCloak
}

// get all ldap mapper for given user federation
func (tk *Tkc) getLdapMapperOfUserFederation(ctx context.Context, idUserFederation string) (mappers map[string]gocloak.Component, getErr error) {
	var mapperList []*gocloak.Component
	mappers = make(map[string]gocloak.Component)

	// get only typed components for given id
	mapperGetParameter := gocloak.GetComponentsParams{
		ProviderType: gocloak.StringP("org.keycloak.storage.ldap.mappers.LDAPStorageMapper"),
		ParentID:     gocloak.StringP(idUserFederation),
	}

	if mapperList, getErr = tk.client.GetComponentsWithParams(ctx, tk.token.AccessToken, tk.realm, mapperGetParameter); getErr != nil {
		return mappers, getErr
	}

	// transform to map with mapper name as map key
	for _, mapper := range mapperList {
		mappers[*mapper.Name] = *mapper
	}

	return mappers, nil
}

// create given group ldap mapper for given user federation
func (tk *Tkc) createGroupMapper(ctx context.Context, idUserFederation string, group string) (createErr error) {
	var existingMappers map[string]gocloak.Component
	var mapper gocloak.Component
	var idMapper string

	if existingMappers, createErr = tk.getLdapMapperOfUserFederation(ctx, idUserFederation); createErr != nil {
		return createErr
	}

	mapper = prepareGroupLdapMapper(group, idUserFederation)
	if existingMapper, exists := existingMappers[group]; exists {
		mapper.ID = existingMapper.ID
		if createErr = tk.client.UpdateComponent(ctx, tk.token.AccessToken, tk.realm, mapper); createErr != nil {
			return createErr
		}
		log.Infof("group ldap mapper '%s' updated (%s)", *mapper.Name, *mapper.ID)
	} else {
		if idMapper, createErr = tk.client.CreateComponent(ctx, tk.token.AccessToken, tk.realm, mapper); createErr != nil {
			return createErr
		}
		log.Infof("group ldap mapper '%s' created (%s)", *mapper.Name, idMapper)
	}

	return nil
}

// prepare component object of type 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper' with config
func prepareGroupLdapMapper(name, idUserFederation string) (mapper gocloak.Component) {
	mapperConfig := make(map[string][]string)

	mapperConfig["mode"] = []string{"LDAP_ONLY"}
	mapperConfig["membership.attribute.type"] = []string{"DN"}
	mapperConfig["user.roles.retrieve.strategy"] = []string{"LOAD_GROUPS_BY_MEMBER_ATTRIBUTE"}
	mapperConfig["group.name.ldap.attribute"] = []string{"cn"}
	mapperConfig["membership.ldap.attribute"] = []string{"member"}
	mapperConfig["preserve.group.inheritance"] = []string{"true"}
	mapperConfig["membership.user.ldap.attribute"] = []string{"uid"}
	mapperConfig["ignore.missing.group"] = []string{"false"}
	mapperConfig["memberof.ldap.attribute"] = []string{"memberOf"}
	mapperConfig["groups.dn"] = []string{"ou=groups,dc=example,dc=com"}
	mapperConfig["group.object.classes"] = []string{"groupOfNames"}
	mapperConfig["drop.non.existing.groups.during.sync"] = []string{"false"}

	mapper = gocloak.Component{
		Name:            gocloak.StringP(name),
		ProviderID:      gocloak.StringP("group-ldap-mapper"),
		ProviderType:    gocloak.StringP("org.keycloak.storage.ldap.mappers.LDAPStorageMapper"),
		ParentID:        gocloak.StringP(idUserFederation),
		ComponentConfig: &mapperConfig,
	}

	return mapper
}

// From last example: But with Mappers
func (tkc *Tkc) newUserFederation(ctx context.Context) (userFederation gocloak.Component, newErr error) {
	var idRealm string

	// get realm ID, is needed as ParentID in gocloak.Component
	if realms, newErr := tkc.getRealms(ctx); newErr != nil {
		return userFederation, newErr
	} else {
		idRealm = *realms[tkc.realm].ID
	}

	userFederationConfig := make(map[string][]string)

	// keycloak self
	userFederationConfig["enabled"] = []string{"true"}
	userFederationConfig["priority"] = []string{"1"}

	// sync options
	userFederationConfig["fullSyncPeriod"] = []string{"-1"}
	userFederationConfig["changedSyncPeriod"] = []string{"300"}
	userFederationConfig["batchSizeForSync"] = []string{"1000"}
	userFederationConfig["importUsers"] = []string{"true"}

	// ldap connection
	userFederationConfig["editMode"] = []string{"READ_ONLY"}
	userFederationConfig["vendor"] = []string{"other"}
	userFederationConfig["connectionUrl"] = []string{"ldap://ldap"}
	userFederationConfig["bindDn"] = []string{"cn=XXX,dc=example,dc=com"}
	userFederationConfig["bindCredential"] = []string{"YYYYYY"}
	userFederationConfig["usersDn"] = []string{"ou=users,dc=example,dc=com"}
	userFederationConfig["usernameLDAPAttribute"] = []string{"uid"}
	userFederationConfig["uuidLDAPAttribute"] = []string{"entryUUID"}
	userFederationConfig["authType"] = []string{"simple"}
	userFederationConfig["userObjectClasses"] = []string{"person, uidObject"}
	userFederationConfig["rdnLDAPAttribute"] = []string{"cn"}
	userFederationConfig["searchScope"] = []string{"1"}
	userFederationConfig["pagination"] = []string{"true"}

	userFederation = gocloak.Component{
		Name:            gocloak.StringP("ldap"),
		ProviderID:      gocloak.StringP("ldap"),
		ProviderType:    gocloak.StringP("org.keycloak.storage.UserStorageProvider"),
		ParentID:        gocloak.StringP(idRealm),
		ComponentConfig: &userFederationConfig,
	}

	return userFederation, nil
}

func (tkc *Tkc) RunCreateUserFederation() error {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

	defer func() {
		cancel()
	}()

	// user federation is a keycloak component with type 'org.keycloak.storage.UserStorageProvider'
	var userFederation gocloak.Component

	// ldap mappers for legacy user federation
	userModelAttributeMappers := make(map[string]string)
	userModelAttributeMappers["office"] = "physicalDeliveryOfficeName"
	userModelAttributeMappers["department"] = "destinationIndicator"
	userModelAttributeMappers["address"] = "postalAddress"
	userModelAttributeMappers["telephone"] = "telephoneNumber"
	userModelAttributeMappers["fax"] = "facsimileTelephoneNumber"
	userModelAttributeMappers["title"] = "title"
	userModelAttributeMappers["firstName"] = "cn"

	// ldap group mapper name
	groupMapper := "group"

	// get default new legacy user federation to create or update an existing one
	if newUserFederation, runErr := tkc.newUserFederation(ctx); runErr != nil {
		return runErr
	} else {
		userFederation = newUserFederation
	}

	// search parameter to get exactly legacy user federation which belongs to given realm (parent)
	ldapGetParams := gocloak.GetComponentsParams{
		Name:         userFederation.Name,
		ProviderType: userFederation.ProviderType,
		ParentID:     userFederation.ParentID,
	}

	if comps, getErr := tkc.client.GetComponentsWithParams(ctx, tkc.token.AccessToken, tkc.realm, ldapGetParams); getErr != nil {
		return getErr
	} else {
		// means user federation not found for given realm
		if len(comps) == 0 {
			if idUserFederation, createErr := tkc.client.CreateComponent(ctx, tkc.token.AccessToken, tkc.realm, userFederation); createErr != nil {
				return createErr
			} else {
				// do create federation group ldap mappers
				if createErr := tkc.createGroupMapper(ctx, idUserFederation, groupMapper); createErr != nil {
					return createErr
				}
				// do full sync after creating new one
				if syncErr := tkc.syncUserFederation(ctx, idUserFederation, true); syncErr != nil {
					syncErr = fmt.Errorf("legacy user federation '%s' created (%s), but sync failed", *userFederation.Name, idUserFederation)
					return syncErr
				}
			}
		} else {
			// set ID of user federation for update exactly existing user federation
			userFederation.ID = comps[0].ID
			if updateErr := tkc.client.UpdateComponent(ctx, tkc.token.AccessToken, tkc.baseConfig.Realm, userFederation); updateErr != nil {
				return updateErr
			} else {
				// do change sync only after update exiting one
				if syncErr := tkc.syncUserFederation(ctx, *userFederation.ID, false); syncErr != nil {
					syncErr = fmt.Errorf("legacy user federation '%s' updated (%s), but sync failed", *userFederation.Name, *userFederation.ID)
					return syncErr
				}
			}
		}
	}

	return nil
}