bug6099: added writing persistence and replication for LockingAndBanning

This commit is contained in:
Axel Uhl
2025-03-17 17:45:11 +01:00
parent 2416b49f89
commit eb1b88f4f5
11 changed files with 139 additions and 82 deletions
@@ -3,10 +3,8 @@ package com.sap.sse.security;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -17,7 +15,6 @@ import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import com.sap.sse.security.impl.Activator;
import com.sap.sse.security.impl.PermissionConverter;
@@ -31,6 +28,7 @@ import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.shared.WildcardPermission;
import com.sap.sse.security.shared.impl.Role;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.util.ServiceTrackerFactory;
/**
* This class implements a realm that combines Access Control Lists, Role Based Permission Modeling and
@@ -68,74 +66,14 @@ public abstract class AbstractCompositeAuthorizingRealm extends AuthorizingRealm
setPermissionResolver(wildcardString->new org.apache.shiro.authz.permission.WildcardPermission(wildcardString, /* caseSensitive */ true));
BundleContext context = Activator.getContext();
if (context != null) {
userStore = createUserStoreFuture(context);
accessControlStore = createAccessControlStoreFuture(context);
userStore = ServiceTrackerFactory.createServiceFuture(context, UserStore.class);
accessControlStore = ServiceTrackerFactory.createServiceFuture(context, AccessControlStore.class);
} else {
userStore = null;
accessControlStore = null;
}
}
private Future<UserStore> createUserStoreFuture(BundleContext bundleContext) {
final ServiceTracker<UserStore, UserStore> tracker = new ServiceTracker<>(bundleContext, UserStore.class, /* customizer */ null);
tracker.open();
final FutureTask<UserStore> result = new FutureTask<>(new Callable<UserStore>() {
@Override
public UserStore call() throws InterruptedException {
try {
logger.info("Waiting for UserStore service...");
UserStore userStore = tracker.waitForService(0);
logger.info("Obtained UserStore service "+userStore);
return userStore;
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Interrupted while waiting for UserStore service", e);
throw e;
}
}
});
new Thread("ServiceTracker waiting for UserStore service") {
@Override
public void run() {
try {
result.run();
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception while waiting for UserStore service", e);
}
}
}.start();
return result;
}
private Future<AccessControlStore> createAccessControlStoreFuture(BundleContext bundleContext) {
final ServiceTracker<AccessControlStore, AccessControlStore> tracker = new ServiceTracker<>(bundleContext, AccessControlStore.class, /* customizer */ null);
tracker.open();
final FutureTask<AccessControlStore> result = new FutureTask<>(new Callable<AccessControlStore>() {
@Override
public AccessControlStore call() throws InterruptedException {
try {
logger.info("Waiting for AccessControlListStore service...");
AccessControlStore accessControlStore = tracker.waitForService(0);
logger.info("Obtained AccessControlListStore service "+accessControlStore);
return accessControlStore;
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Interrupted while waiting for AccessControlListStore service", e);
throw e;
}
}
});
new Thread("ServiceTracker waiting for AccessControlListStore service") {
@Override
public void run() {
try {
result.run();
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception while waiting for AccessControlListStore service", e);
}
}
}.start();
return result;
}
protected UserStore getUserStore() {
UserStore result;
if (testUserStore != null) {
@@ -1,5 +1,8 @@
package com.sap.sse.security;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.shiro.authc.AuthenticationException;
@@ -9,11 +12,34 @@ import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.realm.Realm;
import com.sap.sse.security.impl.Activator;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.util.ServiceTrackerFactory;
public class AtLeastOneSuccessfulStrategyWithLockingAndBanning extends AtLeastOneSuccessfulStrategy {
private static final Logger logger = Logger
.getLogger(AtLeastOneSuccessfulStrategyWithLockingAndBanning.class.getName());
private static final Logger logger = Logger.getLogger(AtLeastOneSuccessfulStrategyWithLockingAndBanning.class.getName());
private final Future<SecurityService> securityService;
public AtLeastOneSuccessfulStrategyWithLockingAndBanning() {
if (Activator.getContext() != null) {
securityService = ServiceTrackerFactory.createServiceFuture(Activator.getContext(), SecurityService.class);
} else {
securityService = null;
}
}
private SecurityService getSecurityService() {
SecurityService result;
try {
result = securityService == null ? null : securityService.get();
} catch (InterruptedException | ExecutionException e) {
logger.log(Level.SEVERE, "Error retrieving security service", e);
result = null;
}
return result;
}
@Override
public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo,
AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {
@@ -25,15 +51,22 @@ public class AtLeastOneSuccessfulStrategyWithLockingAndBanning extends AtLeastOn
if (t != null) {
if (t instanceof IncorrectCredentialsException) {
logger.info("failed password authentication for user "+username);
user.getLockingAndBanning().failedPasswordAuthentication();
final SecurityService mySecurityService = getSecurityService();
if (mySecurityService != null) {
mySecurityService.failedPasswordAuthentication(user);
} else {
logger.warning("Account locking due to failed password authentication for user "+username+" not possible; security service not found");
}
}
} else {
// no exception, so the authentication must have been successful
user.getLockingAndBanning().successfulPasswordAuthentication();
final SecurityService mySecurityService = getSecurityService();
if (mySecurityService != null) {
mySecurityService.successfulPasswordAuthentication(user);
}
}
}
}
return super.afterAttempt(realm, token, singleRealmInfo, aggregateInfo, t);
}
}
@@ -24,13 +24,16 @@ public class BearerTokenRealm extends AbstractCompositeAuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
final AuthenticationInfo result;
BearerAuthenticationToken accessToken = (BearerAuthenticationToken) token;
final User user = getUserStore().getUserByAccessToken(accessToken.getCredentials());
if (user == null) {
return null;
result = null;
} else {
// return salted credentials
SaltedAuthenticationInfo sai = new SimpleSaltedAuthenticationInfo(user.getName(), accessToken.getCredentials(), /* salt */ null);
result = sai;
}
// return salted credentials
SaltedAuthenticationInfo sai = new SimpleSaltedAuthenticationInfo(user.getName(), accessToken.getCredentials(), /* salt */ null);
return sai;
return result;
}
}
@@ -859,4 +859,8 @@ public interface SecurityService extends ReplicableWithObjectInputStream<Replica
void setCORSFilterConfigurationAllowedOrigins(String serverName, String... allowedOrigins) throws IllegalArgumentException;
Pair<Boolean, Set<String>> getCORSFilterConfiguration(String serverName);
void failedPasswordAuthentication(User user);
void successfulPasswordAuthentication(User user);
}
@@ -33,7 +33,7 @@ public class UsernamePasswordRealm extends AbstractCompositeAuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;
final UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;
final String username = userPassToken.getUsername();
if (username == null) {
return null;
@@ -41,14 +41,14 @@ public class UsernamePasswordRealm extends AbstractCompositeAuthorizingRealm {
// read password hash and salt from db
String saltedPassword = null;
byte[] salt = null;
User user = getUserStore().getUserByName(username);
final User user = getUserStore().getUserByName(username);
if (user == null) {
return null;
}
if (user.getLockingAndBanning().isPasswordAuthenticationLocked()) {
throw new LockedAccountException("Password authentication for user "+username+" is currently locked");
}
UsernamePasswordAccount upa = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD);
final UsernamePasswordAccount upa = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD);
if (upa == null){
return null;
}
@@ -120,4 +120,8 @@ public interface ReplicableSecurityService extends SecurityService {
Void internalSetCORSFilterConfigurationToWildcard(String serverName);
Void internalSetCORSFilterConfigurationAllowedOrigins(String serverName, String... allowedOrigins);
Void internalFailedPasswordAuthentication(String username);
Void internalSuccessfulPasswordAuthentication(String username);
}
@@ -1170,13 +1170,43 @@ implements ReplicableSecurityService, ClearStateTestSupport {
final boolean result = Util.equalsWithNull(hashedOldPassword, account.getSaltedPassword());
if (!result) {
logger.info("Failed password check for user "+username);
user.getLockingAndBanning().failedPasswordAuthentication();
apply(s->s.internalFailedPasswordAuthentication(username));
} else {
user.getLockingAndBanning().successfulPasswordAuthentication();
apply(s->s.internalSuccessfulPasswordAuthentication(username));
}
return result;
}
@Override
public void failedPasswordAuthentication(User user) {
apply(s->s.internalFailedPasswordAuthentication(user.getName()));
}
@Override
public Void internalFailedPasswordAuthentication(String username) {
final User user = getUserByName(username);
if (user != null) {
user.getLockingAndBanning().failedPasswordAuthentication();
store.updateUser(user);
}
return null;
}
@Override
public void successfulPasswordAuthentication(User user) {
apply(s->s.internalSuccessfulPasswordAuthentication(user.getName()));
}
@Override
public Void internalSuccessfulPasswordAuthentication(String username) {
final User user = getUserByName(username);
if (user != null) {
user.getLockingAndBanning().successfulPasswordAuthentication();
store.updateUser(user);
}
return null;
}
@Override
public boolean checkPasswordResetSecret(String username, String passwordResetSecret) throws UserManagementException {
final User user = store.getUserByName(username);