added support of multiple fallback suffixes
This commit is contained in:
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user