From 8205ffaec377d04010e10d4a37cea6a05ba56bc8 Mon Sep 17 00:00:00 2001 From: Joern Muehlencord Date: Wed, 14 Aug 2019 12:00:45 +0200 Subject: [PATCH] fixed BLOCKED users are accepted --- .../shiro/filter/JWTAuthenticationFilter.java | 2 +- .../account/shiro/realm/AccountRealm.java | 610 +++++++++--------- 2 files changed, 306 insertions(+), 306 deletions(-) diff --git a/account/src/main/java/de/muehlencord/shared/account/shiro/filter/JWTAuthenticationFilter.java b/account/src/main/java/de/muehlencord/shared/account/shiro/filter/JWTAuthenticationFilter.java index 5cc63e4..8d612a9 100644 --- a/account/src/main/java/de/muehlencord/shared/account/shiro/filter/JWTAuthenticationFilter.java +++ b/account/src/main/java/de/muehlencord/shared/account/shiro/filter/JWTAuthenticationFilter.java @@ -93,7 +93,7 @@ public final class JWTAuthenticationFilter extends AuthenticatingFilter { if (isLoggedAttempt(request, response)) { String jwtToken = getAuthzHeader(request); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("found jwtToke in header = {}", jwtToken); + LOGGER.trace("found jwtToken in header = {}", jwtToken); } if (jwtToken != null) { diff --git a/account/src/main/java/de/muehlencord/shared/account/shiro/realm/AccountRealm.java b/account/src/main/java/de/muehlencord/shared/account/shiro/realm/AccountRealm.java index 1f8238a..d5f76c7 100644 --- a/account/src/main/java/de/muehlencord/shared/account/shiro/realm/AccountRealm.java +++ b/account/src/main/java/de/muehlencord/shared/account/shiro/realm/AccountRealm.java @@ -1,305 +1,305 @@ -/* - * 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 de.muehlencord.shared.account.shiro.authc.JwtMatcher; -import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.UUID; -import org.apache.shiro.authc.AccountException; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.authc.AuthenticationInfo; -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.IncorrectCredentialsException; -import org.apache.shiro.authc.SimpleAuthenticationInfo; -import org.apache.shiro.authc.UnknownAccountException; -import org.apache.shiro.authc.UsernamePasswordToken; -import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher; -import org.apache.shiro.authc.credential.CredentialsMatcher; -import org.apache.shiro.realm.jdbc.JdbcRealm; -import org.apache.shiro.util.JdbcUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author joern.muehlencord - */ -public class AccountRealm extends JdbcRealm { - - private static final Logger LOGGER = LoggerFactory.getLogger(AccountRealm.class); - - protected String applicationId = null; - protected String jwtAuthenticationQuery = "select ak.api_key from account a, api_key ak where ak.account = a.id and a.username = ? and a.status not in ('LOCKED','DELETED','DISABLED') ORDER BY ak.issued_on ASC"; - protected CredentialsMatcher jwtMatcher = new JwtMatcher(); - - public AccountRealm() { - this.authenticationQuery = "select al.account_password from account a, account_login al where al.account = a.id and a.username = ? and status not in ('LOCKED','DELETED','DISABLED')"; - this.userRolesQuery = "select r.role_name from application_role r, account_role ar, account a WHERE a.username = ? AND a.id = ar.account AND ar.account_role = r.id AND r.application = ?"; - this.permissionsQuery = "select permission_name from application_role appr, role_permission rp, application_permission appp WHERE appr.role_name = ? AND appr.application = ? AND rp.application_role = appr.id AND rp.role_permission = appp.id"; - this.permissionsLookupEnabled = true; - } - - @Override - public boolean supports(AuthenticationToken token) { - return (token != null && ((JWTAuthenticationToken.class.isAssignableFrom(token.getClass())) || (UsernamePasswordToken.class.isAssignableFrom(token.getClass())))); - } - - @Override - protected Set getRoleNamesForUser(Connection conn, String username) throws SQLException { - PreparedStatement ps = null; - ResultSet rs = null; - Set roleNames = new LinkedHashSet<>(); - try { - ps = conn.prepareStatement(userRolesQuery); - ps.setString(1, username); - ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm - - // Execute query - rs = ps.executeQuery(); - - // Loop over results and add each returned role to a set - while (rs.next()) { - - String roleName = rs.getString(1); - - // Add the role to the list of names if it isn't null - if (roleName != null) { - roleNames.add(roleName); - } else { - LOGGER.error("Null role name found while retrieving role names for user [{}]", username); - } - } - } finally { - JdbcUtils.closeResultSet(rs); - JdbcUtils.closeStatement(ps); - } - return roleNames; - } - - /** - * overwritten getPermissions. Only change is to inject the applicationId - * into the the query - * - * @param conn the connection to use - * @param username the user to lookup - * @param roleNames the users roles - * @return a list of permissions - * @throws SQLException if the SQL query fails - */ - @Override - protected Set getPermissions(Connection conn, String username, Collection roleNames) throws SQLException { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("user {} has the following roles: {}", username, roleNames.toString()); - LOGGER.debug("looking up permissions for user"); - } - - PreparedStatement ps = null; - Set permissions = new LinkedHashSet<>(); - try { - ps = conn.prepareStatement(permissionsQuery); - for (String roleName : roleNames) { - - ps.setString(1, roleName); - ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm - - ResultSet rs = null; - try { - // Execute query - rs = ps.executeQuery(); - // Loop over results and add each returned role to a set - while (rs.next()) { - String permissionString = rs.getString(1); - // Add the permission to the set of permissions - permissions.add(permissionString); - } - } finally { - JdbcUtils.closeResultSet(rs); - } - - } - } finally { - JdbcUtils.closeStatement(ps); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("user {} has the following permissions: {}", username, permissions.toString()); - } - - return permissions; - } - - public boolean isJwtAuthentication(AuthenticationToken token) { - if (token == null) { - throw new AuthenticationException("empty tokens are not supported by this realm"); - } - - if (token.getClass().isAssignableFrom(JWTAuthenticationToken.class)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Handling JWTAuthenticationToken"); - } - return true; - } else if (token.getClass().isAssignableFrom(UsernamePasswordToken.class)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Handling UsernamePasswordToken"); - } - return false; - } else { - throw new AuthenticationException("Handling of " + token.getClass() + " not supported by this realm"); - } - } - - @Override - protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { - if (isJwtAuthentication(token)) { - return doGetJwtAuthenticationInfo(token); - } else { - return super.doGetAuthenticationInfo(token); - } - } - - protected AuthenticationInfo doGetJwtAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { - JWTAuthenticationToken jwtToken = (JWTAuthenticationToken) token; - String username = jwtToken.getUserName(); - - // Null username is invalid - if (username == null) { - throw new AccountException("Null usernames are not allowed by this realm."); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Checking JWT for user {}", username); - } - - Connection conn = null; - SimpleAuthenticationInfo info = null; - try { - conn = dataSource.getConnection(); - - String apiKey = getApiKeyForJwtUser(conn, username); - - if (apiKey == null) { - throw new UnknownAccountException("No api key found for user [" + username + "]"); - } - - info = new SimpleAuthenticationInfo(username, apiKey.toCharArray(), getName()); - - } catch (SQLException ex) { - final String message = "There was a SQL error while authenticating user [" + username + "]"; - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(message, ex); - } else { - LOGGER.error(ex.toString()); - } - // Rethrow any SQL errors as an authentication exception - throw new AuthenticationException(message, ex); - } finally { - JdbcUtils.closeConnection(conn); - } - - return info; - } - - private String getApiKeyForJwtUser(Connection conn, String username) throws SQLException { - - String result = null; - - PreparedStatement ps = null; - ResultSet rs = null; - try { - ps = conn.prepareStatement(jwtAuthenticationQuery); - ps.setString(1, username); - - // Execute query - rs = ps.executeQuery(); - // loop through result, take last one (by default ordered by issue date ASC) - // we only expect one - application should delete all obsolete ones automatically - while (rs.next()) { - result = rs.getString(1); - } - } finally { - JdbcUtils.closeResultSet(rs); - JdbcUtils.closeStatement(ps); - } - - return result; - } - - /** - * Asserts that the submitted {@code AuthenticationToken}'s credentials - * match the stored account {@code AuthenticationInfo}'s credentials, and if - * not, throws an {@link AuthenticationException}. - * - * @param token the submitted authentication token - * @param info the AuthenticationInfo corresponding to the given - * {@code token} - * @throws AuthenticationException if the token's credentials do not match - * the stored account credentials. - */ - @Override - protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException { - CredentialsMatcher cm; - if (isJwtAuthentication(token)) { - cm = getJwtMatcher(); - } else { - cm = getCredentialsMatcher(); - } - - if (cm != null) { - if (!cm.doCredentialsMatch(token, info)) { - //not successful - throw an exception to indicate this: - String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials."; - throw new IncorrectCredentialsException(msg); - } - } else { - throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " - + "credentials during authentication. If you do not wish for credentials to be examined, you " - + "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance."); - } - } - - /* *** getter / setter *** */ - public String getApplicationId() { - return this.applicationId; - } - - public void setApplicationId(String applicationId) { - this.applicationId = applicationId; - } - - public String getJwtAuthenticationQuery() { - return jwtAuthenticationQuery; - } - - public void setJwtAuthenticationQuery(String jwtAuthenticationQuery) { - this.jwtAuthenticationQuery = jwtAuthenticationQuery; - } - - public CredentialsMatcher getJwtMatcher() { - return jwtMatcher; - } - - public void setJwtMatcher(CredentialsMatcher jwtMatcher) { - this.jwtMatcher = jwtMatcher; - } - -} +/* + * 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 de.muehlencord.shared.account.shiro.authc.JwtMatcher; +import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; +import org.apache.shiro.authc.AccountException; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.IncorrectCredentialsException; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UnknownAccountException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher; +import org.apache.shiro.authc.credential.CredentialsMatcher; +import org.apache.shiro.realm.jdbc.JdbcRealm; +import org.apache.shiro.util.JdbcUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author joern.muehlencord + */ +public class AccountRealm extends JdbcRealm { + + private static final Logger LOGGER = LoggerFactory.getLogger(AccountRealm.class); + + protected String applicationId = null; + protected String jwtAuthenticationQuery = "select ak.api_key from account a, api_key ak where ak.account = a.id and a.username = ? and a.status not in ('BLOCKED','DELETED','DISABLED') ORDER BY ak.issued_on ASC"; + protected CredentialsMatcher jwtMatcher = new JwtMatcher(); + + public AccountRealm() { + this.authenticationQuery = "select al.account_password from account a, account_login al where al.account = a.id and a.username = ? and status not in ('BLOCKED','DELETED','DISABLED')"; + this.userRolesQuery = "select r.role_name from application_role r, account_role ar, account a WHERE a.username = ? AND a.id = ar.account AND ar.account_role = r.id AND r.application = ?"; + this.permissionsQuery = "select permission_name from application_role appr, role_permission rp, application_permission appp WHERE appr.role_name = ? AND appr.application = ? AND rp.application_role = appr.id AND rp.role_permission = appp.id"; + this.permissionsLookupEnabled = true; + } + + @Override + public boolean supports(AuthenticationToken token) { + return (token != null && ((JWTAuthenticationToken.class.isAssignableFrom(token.getClass())) || (UsernamePasswordToken.class.isAssignableFrom(token.getClass())))); + } + + @Override + protected Set getRoleNamesForUser(Connection conn, String username) throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + Set roleNames = new LinkedHashSet<>(); + try { + ps = conn.prepareStatement(userRolesQuery); + ps.setString(1, username); + ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm + + // Execute query + rs = ps.executeQuery(); + + // Loop over results and add each returned role to a set + while (rs.next()) { + + String roleName = rs.getString(1); + + // Add the role to the list of names if it isn't null + if (roleName != null) { + roleNames.add(roleName); + } else { + LOGGER.error("Null role name found while retrieving role names for user [{}]", username); + } + } + } finally { + JdbcUtils.closeResultSet(rs); + JdbcUtils.closeStatement(ps); + } + return roleNames; + } + + /** + * overwritten getPermissions. Only change is to inject the applicationId + * into the the query + * + * @param conn the connection to use + * @param username the user to lookup + * @param roleNames the users roles + * @return a list of permissions + * @throws SQLException if the SQL query fails + */ + @Override + protected Set getPermissions(Connection conn, String username, Collection roleNames) throws SQLException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("user {} has the following roles: {}", username, roleNames.toString()); + LOGGER.debug("looking up permissions for user"); + } + + PreparedStatement ps = null; + Set permissions = new LinkedHashSet<>(); + try { + ps = conn.prepareStatement(permissionsQuery); + for (String roleName : roleNames) { + + ps.setString(1, roleName); + ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm + + ResultSet rs = null; + try { + // Execute query + rs = ps.executeQuery(); + // Loop over results and add each returned role to a set + while (rs.next()) { + String permissionString = rs.getString(1); + // Add the permission to the set of permissions + permissions.add(permissionString); + } + } finally { + JdbcUtils.closeResultSet(rs); + } + + } + } finally { + JdbcUtils.closeStatement(ps); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("user {} has the following permissions: {}", username, permissions.toString()); + } + + return permissions; + } + + public boolean isJwtAuthentication(AuthenticationToken token) { + if (token == null) { + throw new AuthenticationException("empty tokens are not supported by this realm"); + } + + if (token.getClass().isAssignableFrom(JWTAuthenticationToken.class)) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Handling JWTAuthenticationToken"); + } + return true; + } else if (token.getClass().isAssignableFrom(UsernamePasswordToken.class)) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Handling UsernamePasswordToken"); + } + return false; + } else { + throw new AuthenticationException("Handling of " + token.getClass() + " not supported by this realm"); + } + } + + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + if (isJwtAuthentication(token)) { + return doGetJwtAuthenticationInfo(token); + } else { + return super.doGetAuthenticationInfo(token); + } + } + + protected AuthenticationInfo doGetJwtAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + JWTAuthenticationToken jwtToken = (JWTAuthenticationToken) token; + String username = jwtToken.getUserName(); + + // Null username is invalid + if (username == null) { + throw new AccountException("Null usernames are not allowed by this realm."); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Checking JWT for user {}", username); + } + + Connection conn = null; + SimpleAuthenticationInfo info = null; + try { + conn = dataSource.getConnection(); + + String apiKey = getApiKeyForJwtUser(conn, username); + + if (apiKey == null) { + throw new UnknownAccountException("No api key found for user [" + username + "]"); + } + + info = new SimpleAuthenticationInfo(username, apiKey.toCharArray(), getName()); + + } catch (SQLException ex) { + final String message = "There was a SQL error while authenticating user [" + username + "]"; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(message, ex); + } else { + LOGGER.error(ex.toString()); + } + // Rethrow any SQL errors as an authentication exception + throw new AuthenticationException(message, ex); + } finally { + JdbcUtils.closeConnection(conn); + } + + return info; + } + + private String getApiKeyForJwtUser(Connection conn, String username) throws SQLException { + + String result = null; + + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = conn.prepareStatement(jwtAuthenticationQuery); + ps.setString(1, username); + + // Execute query + rs = ps.executeQuery(); + // loop through result, take last one (by default ordered by issue date ASC) + // we only expect one - application should delete all obsolete ones automatically + while (rs.next()) { + result = rs.getString(1); + } + } finally { + JdbcUtils.closeResultSet(rs); + JdbcUtils.closeStatement(ps); + } + + return result; + } + + /** + * Asserts that the submitted {@code AuthenticationToken}'s credentials + * match the stored account {@code AuthenticationInfo}'s credentials, and if + * not, throws an {@link AuthenticationException}. + * + * @param token the submitted authentication token + * @param info the AuthenticationInfo corresponding to the given + * {@code token} + * @throws AuthenticationException if the token's credentials do not match + * the stored account credentials. + */ + @Override + protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException { + CredentialsMatcher cm; + if (isJwtAuthentication(token)) { + cm = getJwtMatcher(); + } else { + cm = getCredentialsMatcher(); + } + + if (cm != null) { + if (!cm.doCredentialsMatch(token, info)) { + //not successful - throw an exception to indicate this: + String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials."; + throw new IncorrectCredentialsException(msg); + } + } else { + throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " + + "credentials during authentication. If you do not wish for credentials to be examined, you " + + "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance."); + } + } + + /* *** getter / setter *** */ + public String getApplicationId() { + return this.applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public String getJwtAuthenticationQuery() { + return jwtAuthenticationQuery; + } + + public void setJwtAuthenticationQuery(String jwtAuthenticationQuery) { + this.jwtAuthenticationQuery = jwtAuthenticationQuery; + } + + public CredentialsMatcher getJwtMatcher() { + return jwtMatcher; + } + + public void setJwtMatcher(CredentialsMatcher jwtMatcher) { + this.jwtMatcher = jwtMatcher; + } + +}