/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.storage.ldap.mappers;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.storage.ldap.idm.query.Condition;
import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
import org.keycloak.storage.ldap.idm.query.internal.EqualCondition;
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
import java.util.HashSet;
import java.util.Set;
/**
* Mapper useful for the LDAP deployments when some attribute (usually CN) is mapped to full name of user
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper {
public static final String LDAP_FULL_NAME_ATTRIBUTE = "ldap.full.name.attribute";
public static final String READ_ONLY = "read.only";
public static final String WRITE_ONLY = "write.only";
public FullNameLDAPStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider) {
super(mapperModel, ldapProvider);
}
@Override
public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
if (isWriteOnly()) {
return;
}
String ldapFullNameAttrName = getLdapFullNameAttrName();
String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
if (fullName == null) {
return;
}
fullName = fullName.trim();
if (!fullName.isEmpty()) {
int lastSpaceIndex = fullName.lastIndexOf(" ");
if (lastSpaceIndex == -1) {
user.setLastName(fullName);
} else {
user.setFirstName(fullName.substring(0, lastSpaceIndex));
user.setLastName(fullName.substring(lastSpaceIndex + 1));
}
}
}
@Override
public void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
String ldapFullNameAttrName = getLdapFullNameAttrName();
String fullName = getFullNameForWriteToLDAP(localUser.getFirstName(), localUser.getLastName(), localUser.getUsername());
ldapUser.setSingleAttribute(ldapFullNameAttrName, fullName);
if (isReadOnly()) {
ldapUser.addReadOnlyAttributeName(ldapFullNameAttrName);
}
}
@Override
public UserModel proxy(LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
if (ldapProvider.getEditMode() == UserStorageProvider.EditMode.WRITABLE && !isReadOnly()) {
TxAwareLDAPUserModelDelegate txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapUser) {
@Override
public void setFirstName(String firstName) {
super.setFirstName(firstName);
setFullNameToLDAPObject();
}
@Override
public void setLastName(String lastName) {
super.setLastName(lastName);
setFullNameToLDAPObject();
}
private void setFullNameToLDAPObject() {
String fullName = getFullNameForWriteToLDAP(getFirstName(), getLastName(), getUsername());
if (logger.isTraceEnabled()) {
logger.tracef("Pushing full name attribute to LDAP. Full name: %s", fullName);
}
ensureTransactionStarted();
String ldapFullNameAttrName = getLdapFullNameAttrName();
ldapUser.setSingleAttribute(ldapFullNameAttrName, fullName);
}
};
return txDelegate;
} else {
return delegate;
}
}
@Override
public void beforeLDAPQuery(LDAPQuery query) {
if (isWriteOnly()) {
return;
}
String ldapFullNameAttrName = getLdapFullNameAttrName();
query.addReturningLdapAttribute(ldapFullNameAttrName);
// Change conditions and compute condition for fullName from the conditions for firstName and lastName. Right now just "equal" condition is supported
EqualCondition firstNameCondition = null;
EqualCondition lastNameCondition = null;
Set<Condition> conditionsCopy = new HashSet<Condition>(query.getConditions());
for (Condition condition : conditionsCopy) {
String paramName = condition.getParameterName();
if (paramName != null) {
if (paramName.equals(UserModel.FIRST_NAME)) {
firstNameCondition = (EqualCondition) condition;
query.getConditions().remove(condition);
} else if (paramName.equals(UserModel.LAST_NAME)) {
lastNameCondition = (EqualCondition) condition;
query.getConditions().remove(condition);
} else if (paramName.equals(LDAPConstants.GIVENNAME)) {
// Some previous mapper already converted it to LDAP name
firstNameCondition = (EqualCondition) condition;
} else if (paramName.equals(LDAPConstants.SN)) {
// Some previous mapper already converted it to LDAP name
lastNameCondition = (EqualCondition) condition;
}
}
}
String fullName = null;
if (firstNameCondition != null && lastNameCondition != null) {
fullName = firstNameCondition.getValue() + " " + lastNameCondition.getValue();
} else if (firstNameCondition != null) {
fullName = (String) firstNameCondition.getValue();
} else if (lastNameCondition != null) {
fullName = (String) lastNameCondition.getValue();
} else {
return;
}
EscapeStrategy escapeStrategy = firstNameCondition!=null ? firstNameCondition.getEscapeStrategy() : lastNameCondition.getEscapeStrategy();
EqualCondition fullNameCondition = new EqualCondition(ldapFullNameAttrName, fullName, escapeStrategy);
query.addWhereCondition(fullNameCondition);
}
protected String getLdapFullNameAttrName() {
String ldapFullNameAttrName = mapperModel.getConfig().getFirst(LDAP_FULL_NAME_ATTRIBUTE);
return ldapFullNameAttrName == null ? LDAPConstants.CN : ldapFullNameAttrName;
}
protected String getFullNameForWriteToLDAP(String firstName, String lastName, String username) {
if (!isBlank(firstName) && !isBlank(lastName)) {
return firstName + " " + lastName;
} else if (!isBlank(firstName)) {
return firstName;
} else if (!isBlank(lastName)) {
return lastName;
} else if (isFallbackToUsername()) {
return username;
} else {
return LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
}
}
private boolean isBlank(String attr) {
return attr == null || attr.trim().isEmpty();
}
private boolean isReadOnly() {
return parseBooleanParameter(mapperModel, READ_ONLY);
}
private boolean isWriteOnly() {
return parseBooleanParameter(mapperModel, WRITE_ONLY);
}
// Used just in case that we have Writable LDAP and fullName is mapped to "cn", which is used as RDN (this typically happens only on MSAD)
private boolean isFallbackToUsername() {
String rdnLdapAttrConfig = getLdapProvider().getLdapIdentityStore().getConfig().getRdnLdapAttribute();
return !isReadOnly() && getLdapFullNameAttrName().equalsIgnoreCase(rdnLdapAttrConfig);
}
}