fixed BLOCKED users are accepted

This commit is contained in:
Joern Muehlencord
2019-08-14 12:00:45 +02:00
parent 54f2e56a4c
commit 8205ffaec3
2 changed files with 306 additions and 306 deletions

View File

@ -93,7 +93,7 @@ public final class JWTAuthenticationFilter extends AuthenticatingFilter {
if (isLoggedAttempt(request, response)) { if (isLoggedAttempt(request, response)) {
String jwtToken = getAuthzHeader(request); String jwtToken = getAuthzHeader(request);
if (LOGGER.isTraceEnabled()) { if (LOGGER.isTraceEnabled()) {
LOGGER.trace("found jwtToke in header = {}", jwtToken); LOGGER.trace("found jwtToken in header = {}", jwtToken);
} }
if (jwtToken != null) { if (jwtToken != null) {

View File

@ -1,305 +1,305 @@
/* /*
* 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 de.muehlencord.shared.account.shiro.authc.JwtMatcher; import de.muehlencord.shared.account.shiro.authc.JwtMatcher;
import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken; import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import org.apache.shiro.authc.AccountException; import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher; import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.realm.jdbc.JdbcRealm; import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.JdbcUtils; import org.apache.shiro.util.JdbcUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* *
* @author joern.muehlencord * @author joern.muehlencord
*/ */
public class AccountRealm extends JdbcRealm { public class AccountRealm extends JdbcRealm {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountRealm.class); private static final Logger LOGGER = LoggerFactory.getLogger(AccountRealm.class);
protected String applicationId = null; 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 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(); protected CredentialsMatcher jwtMatcher = new JwtMatcher();
public AccountRealm() { 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.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.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.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; this.permissionsLookupEnabled = true;
} }
@Override @Override
public boolean supports(AuthenticationToken token) { public boolean supports(AuthenticationToken token) {
return (token != null && ((JWTAuthenticationToken.class.isAssignableFrom(token.getClass())) || (UsernamePasswordToken.class.isAssignableFrom(token.getClass())))); return (token != null && ((JWTAuthenticationToken.class.isAssignableFrom(token.getClass())) || (UsernamePasswordToken.class.isAssignableFrom(token.getClass()))));
} }
@Override @Override
protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException { protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
Set<String> roleNames = new LinkedHashSet<>(); Set<String> roleNames = new LinkedHashSet<>();
try { try {
ps = conn.prepareStatement(userRolesQuery); ps = conn.prepareStatement(userRolesQuery);
ps.setString(1, username); ps.setString(1, username);
ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm
// Execute query // Execute query
rs = ps.executeQuery(); rs = ps.executeQuery();
// Loop over results and add each returned role to a set // Loop over results and add each returned role to a set
while (rs.next()) { while (rs.next()) {
String roleName = rs.getString(1); String roleName = rs.getString(1);
// Add the role to the list of names if it isn't null // Add the role to the list of names if it isn't null
if (roleName != null) { if (roleName != null) {
roleNames.add(roleName); roleNames.add(roleName);
} else { } else {
LOGGER.error("Null role name found while retrieving role names for user [{}]", username); LOGGER.error("Null role name found while retrieving role names for user [{}]", username);
} }
} }
} finally { } finally {
JdbcUtils.closeResultSet(rs); JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps); JdbcUtils.closeStatement(ps);
} }
return roleNames; return roleNames;
} }
/** /**
* overwritten getPermissions. Only change is to inject the applicationId * overwritten getPermissions. Only change is to inject the applicationId
* into the the query * into the the query
* *
* @param conn the connection to use * @param conn the connection to use
* @param username the user to lookup * @param username the user to lookup
* @param roleNames the users roles * @param roleNames the users roles
* @return a list of permissions * @return a list of permissions
* @throws SQLException if the SQL query fails * @throws SQLException if the SQL query fails
*/ */
@Override @Override
protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException { protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException {
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("user {} has the following roles: {}", username, roleNames.toString()); LOGGER.debug("user {} has the following roles: {}", username, roleNames.toString());
LOGGER.debug("looking up permissions for user"); LOGGER.debug("looking up permissions for user");
} }
PreparedStatement ps = null; PreparedStatement ps = null;
Set<String> permissions = new LinkedHashSet<>(); Set<String> permissions = new LinkedHashSet<>();
try { try {
ps = conn.prepareStatement(permissionsQuery); ps = conn.prepareStatement(permissionsQuery);
for (String roleName : roleNames) { for (String roleName : roleNames) {
ps.setString(1, roleName); ps.setString(1, roleName);
ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm
ResultSet rs = null; ResultSet rs = null;
try { try {
// Execute query // Execute query
rs = ps.executeQuery(); rs = ps.executeQuery();
// Loop over results and add each returned role to a set // Loop over results and add each returned role to a set
while (rs.next()) { while (rs.next()) {
String permissionString = rs.getString(1); String permissionString = rs.getString(1);
// Add the permission to the set of permissions // Add the permission to the set of permissions
permissions.add(permissionString); permissions.add(permissionString);
} }
} finally { } finally {
JdbcUtils.closeResultSet(rs); JdbcUtils.closeResultSet(rs);
} }
} }
} finally { } finally {
JdbcUtils.closeStatement(ps); JdbcUtils.closeStatement(ps);
} }
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("user {} has the following permissions: {}", username, permissions.toString()); LOGGER.debug("user {} has the following permissions: {}", username, permissions.toString());
} }
return permissions; return permissions;
} }
public boolean isJwtAuthentication(AuthenticationToken token) { public boolean isJwtAuthentication(AuthenticationToken token) {
if (token == null) { if (token == null) {
throw new AuthenticationException("empty tokens are not supported by this realm"); throw new AuthenticationException("empty tokens are not supported by this realm");
} }
if (token.getClass().isAssignableFrom(JWTAuthenticationToken.class)) { if (token.getClass().isAssignableFrom(JWTAuthenticationToken.class)) {
if (LOGGER.isTraceEnabled()) { if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Handling JWTAuthenticationToken"); LOGGER.trace("Handling JWTAuthenticationToken");
} }
return true; return true;
} else if (token.getClass().isAssignableFrom(UsernamePasswordToken.class)) { } else if (token.getClass().isAssignableFrom(UsernamePasswordToken.class)) {
if (LOGGER.isTraceEnabled()) { if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Handling UsernamePasswordToken"); LOGGER.trace("Handling UsernamePasswordToken");
} }
return false; return false;
} else { } else {
throw new AuthenticationException("Handling of " + token.getClass() + " not supported by this realm"); throw new AuthenticationException("Handling of " + token.getClass() + " not supported by this realm");
} }
} }
@Override @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
if (isJwtAuthentication(token)) { if (isJwtAuthentication(token)) {
return doGetJwtAuthenticationInfo(token); return doGetJwtAuthenticationInfo(token);
} else { } else {
return super.doGetAuthenticationInfo(token); return super.doGetAuthenticationInfo(token);
} }
} }
protected AuthenticationInfo doGetJwtAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { protected AuthenticationInfo doGetJwtAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
JWTAuthenticationToken jwtToken = (JWTAuthenticationToken) token; JWTAuthenticationToken jwtToken = (JWTAuthenticationToken) token;
String username = jwtToken.getUserName(); String username = jwtToken.getUserName();
// Null username is invalid // Null username is invalid
if (username == null) { if (username == null) {
throw new AccountException("Null usernames are not allowed by this realm."); throw new AccountException("Null usernames are not allowed by this realm.");
} }
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Checking JWT for user {}", username); LOGGER.debug("Checking JWT for user {}", username);
} }
Connection conn = null; Connection conn = null;
SimpleAuthenticationInfo info = null; SimpleAuthenticationInfo info = null;
try { try {
conn = dataSource.getConnection(); conn = dataSource.getConnection();
String apiKey = getApiKeyForJwtUser(conn, username); String apiKey = getApiKeyForJwtUser(conn, username);
if (apiKey == null) { if (apiKey == null) {
throw new UnknownAccountException("No api key found for user [" + username + "]"); throw new UnknownAccountException("No api key found for user [" + username + "]");
} }
info = new SimpleAuthenticationInfo(username, apiKey.toCharArray(), getName()); info = new SimpleAuthenticationInfo(username, apiKey.toCharArray(), getName());
} catch (SQLException ex) { } catch (SQLException ex) {
final String message = "There was a SQL error while authenticating user [" + username + "]"; final String message = "There was a SQL error while authenticating user [" + username + "]";
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug(message, ex); LOGGER.debug(message, ex);
} else { } else {
LOGGER.error(ex.toString()); LOGGER.error(ex.toString());
} }
// Rethrow any SQL errors as an authentication exception // Rethrow any SQL errors as an authentication exception
throw new AuthenticationException(message, ex); throw new AuthenticationException(message, ex);
} finally { } finally {
JdbcUtils.closeConnection(conn); JdbcUtils.closeConnection(conn);
} }
return info; return info;
} }
private String getApiKeyForJwtUser(Connection conn, String username) throws SQLException { private String getApiKeyForJwtUser(Connection conn, String username) throws SQLException {
String result = null; String result = null;
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
ps = conn.prepareStatement(jwtAuthenticationQuery); ps = conn.prepareStatement(jwtAuthenticationQuery);
ps.setString(1, username); ps.setString(1, username);
// Execute query // Execute query
rs = ps.executeQuery(); rs = ps.executeQuery();
// loop through result, take last one (by default ordered by issue date ASC) // loop through result, take last one (by default ordered by issue date ASC)
// we only expect one - application should delete all obsolete ones automatically // we only expect one - application should delete all obsolete ones automatically
while (rs.next()) { while (rs.next()) {
result = rs.getString(1); result = rs.getString(1);
} }
} finally { } finally {
JdbcUtils.closeResultSet(rs); JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps); JdbcUtils.closeStatement(ps);
} }
return result; return result;
} }
/** /**
* Asserts that the submitted {@code AuthenticationToken}'s credentials * Asserts that the submitted {@code AuthenticationToken}'s credentials
* match the stored account {@code AuthenticationInfo}'s credentials, and if * match the stored account {@code AuthenticationInfo}'s credentials, and if
* not, throws an {@link AuthenticationException}. * not, throws an {@link AuthenticationException}.
* *
* @param token the submitted authentication token * @param token the submitted authentication token
* @param info the AuthenticationInfo corresponding to the given * @param info the AuthenticationInfo corresponding to the given
* {@code token} * {@code token}
* @throws AuthenticationException if the token's credentials do not match * @throws AuthenticationException if the token's credentials do not match
* the stored account credentials. * the stored account credentials.
*/ */
@Override @Override
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException { protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm; CredentialsMatcher cm;
if (isJwtAuthentication(token)) { if (isJwtAuthentication(token)) {
cm = getJwtMatcher(); cm = getJwtMatcher();
} else { } else {
cm = getCredentialsMatcher(); cm = getCredentialsMatcher();
} }
if (cm != null) { if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) { if (!cm.doCredentialsMatch(token, info)) {
//not successful - throw an exception to indicate this: //not successful - throw an exception to indicate this:
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials."; String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg); throw new IncorrectCredentialsException(msg);
} }
} else { } else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " 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 " + "credentials during authentication. If you do not wish for credentials to be examined, you "
+ "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance."); + "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
} }
} }
/* *** getter / setter *** */ /* *** getter / setter *** */
public String getApplicationId() { public String getApplicationId() {
return this.applicationId; return this.applicationId;
} }
public void setApplicationId(String applicationId) { public void setApplicationId(String applicationId) {
this.applicationId = applicationId; this.applicationId = applicationId;
} }
public String getJwtAuthenticationQuery() { public String getJwtAuthenticationQuery() {
return jwtAuthenticationQuery; return jwtAuthenticationQuery;
} }
public void setJwtAuthenticationQuery(String jwtAuthenticationQuery) { public void setJwtAuthenticationQuery(String jwtAuthenticationQuery) {
this.jwtAuthenticationQuery = jwtAuthenticationQuery; this.jwtAuthenticationQuery = jwtAuthenticationQuery;
} }
public CredentialsMatcher getJwtMatcher() { public CredentialsMatcher getJwtMatcher() {
return jwtMatcher; return jwtMatcher;
} }
public void setJwtMatcher(CredentialsMatcher jwtMatcher) { public void setJwtMatcher(CredentialsMatcher jwtMatcher) {
this.jwtMatcher = jwtMatcher; this.jwtMatcher = jwtMatcher;
} }
} }