added support of multiple fallback suffixes

This commit is contained in:
Joern Muehlencord
2019-08-02 08:45:34 +02:00
parent 24dc927ab7
commit 119fb04520

View File

@ -1,169 +1,179 @@
/* /*
* Copyright 2018 joern.muehlencord. * Copyright 2018 joern.muehlencord.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package de.muehlencord.shared.account.shiro.realm; package de.muehlencord.shared.account.shiro.realm;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Iterator;
import javax.naming.NamingException; import java.util.List;
import javax.naming.ldap.LdapContext; import java.util.Set;
import org.apache.shiro.authc.AuthenticationInfo; import javax.naming.NamingException;
import org.apache.shiro.authc.AuthenticationToken; import javax.naming.ldap.LdapContext;
import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm; import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.ldap.LdapContextFactory; import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.ldap.LdapUtils; import org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm;
import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.realm.ldap.LdapContextFactory;
import org.slf4j.Logger; import org.apache.shiro.realm.ldap.LdapUtils;
import org.slf4j.LoggerFactory; import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
/** import org.slf4j.LoggerFactory;
*
* @author joern.muehlencord /**
*/ *
public class UserNameActiveDirectoryRealm extends ActiveDirectoryRealm { * @author joern.muehlencord
*/
private static final Logger LOGGER = LoggerFactory.getLogger(UserNameActiveDirectoryRealm.class); public class UserNameActiveDirectoryRealm extends ActiveDirectoryRealm {
private boolean permissionsLookupEnabled = true; private static final Logger LOGGER = LoggerFactory.getLogger(UserNameActiveDirectoryRealm.class);
protected String fallbackPrincipalSuffix = null;
private boolean permissionsLookupEnabled = true;
@Override protected List<String> fallbackPrincipalSuffixes = null;
public boolean supports(AuthenticationToken token) { private NamingException lastException = null;
return (token != null && (UsernamePasswordToken.class.isAssignableFrom(token.getClass())));
} @Override
public boolean supports(AuthenticationToken token) {
@Override return (token != null && (UsernamePasswordToken.class.isAssignableFrom(token.getClass())));
protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException { }
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
@Override
String userName = getUserName(upToken, principalSuffix); protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException {
LdapContext ctx = null; UsernamePasswordToken upToken = (UsernamePasswordToken) token;
try { String userName = upToken.getUsername();
ctx = lookupUser(userName, upToken.getCredentials(), ldapContextFactory); LdapContext ctx = lookupUserWithSuffix(upToken, ldapContextFactory, principalSuffix);
} catch (NamingException ex) {
if (fallbackPrincipalSuffix == null) { if (fallbackPrincipalSuffixes != null) {
throw ex; if (LOGGER.isDebugEnabled()) {
} LOGGER.debug("Trying the fallbackSuffixes = {}", fallbackPrincipalSuffixes.toString());
if (LOGGER.isDebugEnabled()) { }
LOGGER.debug("Lookup with principalSuffix {} failed, falling back to {}", principalSuffix, fallbackPrincipalSuffix);
} Iterator<String> it = fallbackPrincipalSuffixes.iterator();
} finally { while ((ctx == null) && (it.hasNext())) {
LdapUtils.closeContext(ctx); ctx = lookupUserWithSuffix(upToken, ldapContextFactory, it.next());
} }
}
if ((ctx == null) && (fallbackPrincipalSuffix != null)) {
userName = getUserName(upToken, fallbackPrincipalSuffix); if (ctx == null) {
try { if (lastException != null) {
ctx = lookupUser(userName, upToken.getCredentials(), ldapContextFactory); throw lastException;
} catch (NamingException ex) { } else {
if (LOGGER.isDebugEnabled()) { throw new NamingException("Unknown error authenticationing user " + userName + ". Context still null. Check implementation");
LOGGER.debug("Lookup with fallbackSuffix {} also failed", fallbackPrincipalSuffix); }
} }
throw ex;
} finally { if (LOGGER.isDebugEnabled()) {
LdapUtils.closeContext(ctx); LOGGER.debug("User {} LDAP authenticated", userName);
} LOGGER.debug("building authentication info");
} }
AuthenticationInfo authInfo = buildAuthenticationInfo(userName, upToken.getPassword());
if (ctx == null) {
throw new NamingException("Unknown error authenticationing user "+userName+". Context still null. Check implementation"); if (LOGGER.isDebugEnabled()) {
} LOGGER.debug("authentifaction info created");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("User {} LDAP authenticated", userName); return authInfo;
} }
LOGGER.debug("building authentication info");
AuthenticationInfo authInfo = buildAuthenticationInfo(userName, upToken.getPassword()); /**
* Builds an {@link org.apache.shiro.authz.AuthorizationInfo} object by querying the active directory LDAP context for the groups that a user is a member of. The groups are then translated to role
LOGGER.debug("authentifaction info created"); * names by using the configured {@link #groupRolesMap}.
return authInfo; *
} * This implementation expects the <tt>principal</tt> argument to be a String username.
*
/** * Subclasses can override this method to determine authorization data (roles, permissions, etc) in a more complex way. Note that this default implementation does not support permissions, only
* Builds an {@link org.apache.shiro.authz.AuthorizationInfo} object by * roles.
* querying the active directory LDAP context for the groups that a user is *
* a member of. The groups are then translated to role names by using the * @param principals the principal of the Subject whose account is being retrieved.
* configured {@link #groupRolesMap}. * @param ldapContextFactory the factory used to create LDAP connections.
* <p/> * @return the AuthorizationInfo for the given Subject principal.
* This implementation expects the <tt>principal</tt> argument to be a * @throws NamingException if an error occurs when searching the LDAP server.
* String username. */
* <p/> @Override
* Subclasses can override this method to determine authorization data protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals, LdapContextFactory ldapContextFactory) throws NamingException {
* (roles, permissions, etc) in a more complex way. Note that this default Set<String> roleNames;
* implementation does not support permissions, only roles. if (this.permissionsLookupEnabled) {
* String username = (String) getAvailablePrincipal(principals);
* @param principals the principal of the Subject whose account is being // Perform context search
* retrieved. LdapContext ldapContext = ldapContextFactory.getSystemLdapContext();
* @param ldapContextFactory the factory used to create LDAP connections. try {
* @return the AuthorizationInfo for the given Subject principal. roleNames = getRoleNamesForUser(username, ldapContext);
* @throws NamingException if an error occurs when searching the LDAP } finally {
* server. LdapUtils.closeContext(ldapContext);
*/ }
@Override } else {
protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals, LdapContextFactory ldapContextFactory) throws NamingException { roleNames = new HashSet<>();
Set<String> roleNames; }
if (this.permissionsLookupEnabled) { return buildAuthorizationInfo(roleNames);
String username = (String) getAvailablePrincipal(principals); }
// Perform context search
LdapContext ldapContext = ldapContextFactory.getSystemLdapContext(); public boolean isPermissionsLookupEnabled() {
try { return permissionsLookupEnabled;
roleNames = getRoleNamesForUser(username, ldapContext); }
} finally {
LdapUtils.closeContext(ldapContext); public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled) {
} this.permissionsLookupEnabled = permissionsLookupEnabled;
} else { }
roleNames = new HashSet<>();
} public List<String> getFallbackPrincipalSuffixes() {
return buildAuthorizationInfo(roleNames); return fallbackPrincipalSuffixes;
} }
public boolean isPermissionsLookupEnabled() { public void setFallbackPrincipalSuffixes(List<String> fallbackPrincipalSuffixes) {
return permissionsLookupEnabled; this.fallbackPrincipalSuffixes = fallbackPrincipalSuffixes;
} }
public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled) { private String getUserName(UsernamePasswordToken upToken, String suffix) {
this.permissionsLookupEnabled = permissionsLookupEnabled; String userName = upToken.getUsername();
} if (suffix != null) {
if (!userName.contains(suffix)) {
public String getFallbackPrincipalSuffix() { userName += suffix;
return fallbackPrincipalSuffix; }
} }
return userName;
public void setFallbackPrincipalSuffix(String fallbackPrincipalSuffix) { }
this.fallbackPrincipalSuffix = fallbackPrincipalSuffix;
} private LdapContext lookupUser(String userName, Object credentials, LdapContextFactory ldapContextFactory) throws NamingException {
private String getUserName(UsernamePasswordToken upToken, String suffix) { // Binds using the username and password provided by the user.
String userName = upToken.getUsername(); if (LOGGER.isDebugEnabled()) {
if (suffix != null) { LOGGER.debug("start creating context");
if (!userName.contains(suffix)) { }
userName += suffix; return ldapContextFactory.getLdapContext(userName, credentials);
} }
}
return userName; private LdapContext lookupUserWithSuffix(UsernamePasswordToken upToken, LdapContextFactory ldapContextFactory, String currentSuffix) {
} String userName = getUserName(upToken, currentSuffix);
LdapContext ctx = null;
private LdapContext lookupUser(String userName, Object credentials, LdapContextFactory ldapContextFactory) throws NamingException { try {
ctx = lookupUser(userName, upToken.getCredentials(), ldapContextFactory);
// Binds using the username and password provided by the user. } catch (NamingException ex) {
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("start creating context"); LOGGER.debug("Lookup with suffix {} failed", currentSuffix);
} }
return ldapContextFactory.getLdapContext(userName, credentials); if (LOGGER.isTraceEnabled()) {
} LOGGER.error(ex.getMessage());
LOGGER.trace("Detailed stacktrace", new Object[]{ex});
} }
lastException = ex;
return null;
} finally {
LdapUtils.closeContext(ctx);
}
return ctx;
}
}