diff --git a/account/src/main/java/de/muehlencord/shared/account/shiro/realm/UserNameActiveDirectoryRealm.java b/account/src/main/java/de/muehlencord/shared/account/shiro/realm/UserNameActiveDirectoryRealm.java index 97b0dbc..7f0b1a9 100644 --- a/account/src/main/java/de/muehlencord/shared/account/shiro/realm/UserNameActiveDirectoryRealm.java +++ b/account/src/main/java/de/muehlencord/shared/account/shiro/realm/UserNameActiveDirectoryRealm.java @@ -1,169 +1,179 @@ -/* - * Copyright 2018 joern.muehlencord. - * - * 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 de.muehlencord.shared.account.shiro.realm; - -import java.util.HashSet; -import java.util.Set; -import javax.naming.NamingException; -import javax.naming.ldap.LdapContext; -import org.apache.shiro.authc.AuthenticationInfo; -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.UsernamePasswordToken; -import org.apache.shiro.authz.AuthorizationInfo; -import org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm; -import org.apache.shiro.realm.ldap.LdapContextFactory; -import org.apache.shiro.realm.ldap.LdapUtils; -import org.apache.shiro.subject.PrincipalCollection; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author joern.muehlencord - */ -public class UserNameActiveDirectoryRealm extends ActiveDirectoryRealm { - - private static final Logger LOGGER = LoggerFactory.getLogger(UserNameActiveDirectoryRealm.class); - - private boolean permissionsLookupEnabled = true; - protected String fallbackPrincipalSuffix = null; - - @Override - public boolean supports(AuthenticationToken token) { - return (token != null && (UsernamePasswordToken.class.isAssignableFrom(token.getClass()))); - } - - @Override - protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException { - UsernamePasswordToken upToken = (UsernamePasswordToken) token; - - String userName = getUserName(upToken, principalSuffix); - LdapContext ctx = null; - try { - ctx = lookupUser(userName, upToken.getCredentials(), ldapContextFactory); - } catch (NamingException ex) { - if (fallbackPrincipalSuffix == null) { - throw ex; - } - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Lookup with principalSuffix {} failed, falling back to {}", principalSuffix, fallbackPrincipalSuffix); - } - } finally { - LdapUtils.closeContext(ctx); - } - - if ((ctx == null) && (fallbackPrincipalSuffix != null)) { - userName = getUserName(upToken, fallbackPrincipalSuffix); - try { - ctx = lookupUser(userName, upToken.getCredentials(), ldapContextFactory); - } catch (NamingException ex) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Lookup with fallbackSuffix {} also failed", fallbackPrincipalSuffix); - } - throw ex; - } finally { - LdapUtils.closeContext(ctx); - } - } - - if (ctx == null) { - throw new NamingException("Unknown error authenticationing user "+userName+". Context still null. Check implementation"); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("User {} LDAP authenticated", userName); - } - LOGGER.debug("building authentication info"); - AuthenticationInfo authInfo = buildAuthenticationInfo(userName, upToken.getPassword()); - - LOGGER.debug("authentifaction info created"); - return authInfo; - } - - /** - * 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 names by using the - * configured {@link #groupRolesMap}. - *

- * This implementation expects the principal 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 roles. - * - * @param principals the principal of the Subject whose account is being - * retrieved. - * @param ldapContextFactory the factory used to create LDAP connections. - * @return the AuthorizationInfo for the given Subject principal. - * @throws NamingException if an error occurs when searching the LDAP - * server. - */ - @Override - protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals, LdapContextFactory ldapContextFactory) throws NamingException { - Set roleNames; - if (this.permissionsLookupEnabled) { - String username = (String) getAvailablePrincipal(principals); - // Perform context search - LdapContext ldapContext = ldapContextFactory.getSystemLdapContext(); - try { - roleNames = getRoleNamesForUser(username, ldapContext); - } finally { - LdapUtils.closeContext(ldapContext); - } - } else { - roleNames = new HashSet<>(); - } - return buildAuthorizationInfo(roleNames); - } - - public boolean isPermissionsLookupEnabled() { - return permissionsLookupEnabled; - } - - public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled) { - this.permissionsLookupEnabled = permissionsLookupEnabled; - } - - public String getFallbackPrincipalSuffix() { - return fallbackPrincipalSuffix; - } - - public void setFallbackPrincipalSuffix(String fallbackPrincipalSuffix) { - this.fallbackPrincipalSuffix = fallbackPrincipalSuffix; - } - - private String getUserName(UsernamePasswordToken upToken, String suffix) { - String userName = upToken.getUsername(); - if (suffix != null) { - if (!userName.contains(suffix)) { - userName += suffix; - } - } - return userName; - } - - private LdapContext lookupUser(String userName, Object credentials, LdapContextFactory ldapContextFactory) throws NamingException { - - // Binds using the username and password provided by the user. - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("start creating context"); - } - return ldapContextFactory.getLdapContext(userName, credentials); - } - -} +/* + * Copyright 2018 joern.muehlencord. + * + * 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 de.muehlencord.shared.account.shiro.realm; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.naming.NamingException; +import javax.naming.ldap.LdapContext; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm; +import org.apache.shiro.realm.ldap.LdapContextFactory; +import org.apache.shiro.realm.ldap.LdapUtils; +import org.apache.shiro.subject.PrincipalCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author joern.muehlencord + */ +public class UserNameActiveDirectoryRealm extends ActiveDirectoryRealm { + + private static final Logger LOGGER = LoggerFactory.getLogger(UserNameActiveDirectoryRealm.class); + + private boolean permissionsLookupEnabled = true; + protected List fallbackPrincipalSuffixes = null; + private NamingException lastException = null; + + @Override + public boolean supports(AuthenticationToken token) { + return (token != null && (UsernamePasswordToken.class.isAssignableFrom(token.getClass()))); + } + + @Override + protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException { + UsernamePasswordToken upToken = (UsernamePasswordToken) token; + String userName = upToken.getUsername(); + LdapContext ctx = lookupUserWithSuffix(upToken, ldapContextFactory, principalSuffix); + + if (fallbackPrincipalSuffixes != null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Trying the fallbackSuffixes = {}", fallbackPrincipalSuffixes.toString()); + } + + Iterator it = fallbackPrincipalSuffixes.iterator(); + while ((ctx == null) && (it.hasNext())) { + ctx = lookupUserWithSuffix(upToken, ldapContextFactory, it.next()); + } + } + + if (ctx == null) { + if (lastException != null) { + throw lastException; + } else { + throw new NamingException("Unknown error authenticationing user " + userName + ". Context still null. Check implementation"); + } + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("User {} LDAP authenticated", userName); + LOGGER.debug("building authentication info"); + } + AuthenticationInfo authInfo = buildAuthenticationInfo(userName, upToken.getPassword()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("authentifaction info created"); + } + + return authInfo; + } + + /** + * 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 + * names by using the configured {@link #groupRolesMap}. + * + * This implementation expects the principal 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 + * roles. + * + * @param principals the principal of the Subject whose account is being retrieved. + * @param ldapContextFactory the factory used to create LDAP connections. + * @return the AuthorizationInfo for the given Subject principal. + * @throws NamingException if an error occurs when searching the LDAP server. + */ + @Override + protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals, LdapContextFactory ldapContextFactory) throws NamingException { + Set roleNames; + if (this.permissionsLookupEnabled) { + String username = (String) getAvailablePrincipal(principals); + // Perform context search + LdapContext ldapContext = ldapContextFactory.getSystemLdapContext(); + try { + roleNames = getRoleNamesForUser(username, ldapContext); + } finally { + LdapUtils.closeContext(ldapContext); + } + } else { + roleNames = new HashSet<>(); + } + return buildAuthorizationInfo(roleNames); + } + + public boolean isPermissionsLookupEnabled() { + return permissionsLookupEnabled; + } + + public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled) { + this.permissionsLookupEnabled = permissionsLookupEnabled; + } + + public List getFallbackPrincipalSuffixes() { + return fallbackPrincipalSuffixes; + } + + public void setFallbackPrincipalSuffixes(List fallbackPrincipalSuffixes) { + this.fallbackPrincipalSuffixes = fallbackPrincipalSuffixes; + } + + private String getUserName(UsernamePasswordToken upToken, String suffix) { + String userName = upToken.getUsername(); + if (suffix != null) { + if (!userName.contains(suffix)) { + userName += suffix; + } + } + return userName; + } + + private LdapContext lookupUser(String userName, Object credentials, LdapContextFactory ldapContextFactory) throws NamingException { + + // Binds using the username and password provided by the user. + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("start creating context"); + } + return ldapContextFactory.getLdapContext(userName, credentials); + } + + private LdapContext lookupUserWithSuffix(UsernamePasswordToken upToken, LdapContextFactory ldapContextFactory, String currentSuffix) { + String userName = getUserName(upToken, currentSuffix); + LdapContext ctx = null; + try { + ctx = lookupUser(userName, upToken.getCredentials(), ldapContextFactory); + } catch (NamingException ex) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Lookup with suffix {} failed", currentSuffix); + } + if (LOGGER.isTraceEnabled()) { + LOGGER.error(ex.getMessage()); + LOGGER.trace("Detailed stacktrace", new Object[]{ex}); + } + + lastException = ex; + return null; + } finally { + LdapUtils.closeContext(ctx); + } + return ctx; + } + +}