mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-08 20:12:23 +00:00
2503 lines
113 KiB
Java
2503 lines
113 KiB
Java
package com.sap.sse.security.impl;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.ObjectInputStream;
|
|
import java.io.ObjectOutputStream;
|
|
import java.io.Serializable;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URLEncoder;
|
|
import java.text.MessageFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.Callable;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ConcurrentMap;
|
|
import java.util.concurrent.ConcurrentSkipListSet;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Function;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import javax.servlet.Filter;
|
|
import javax.servlet.ServletContext;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
import org.apache.shiro.SecurityUtils;
|
|
import org.apache.shiro.authc.AuthenticationException;
|
|
import org.apache.shiro.authc.IncorrectCredentialsException;
|
|
import org.apache.shiro.authc.LockedAccountException;
|
|
import org.apache.shiro.authc.UnknownAccountException;
|
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
|
import org.apache.shiro.authz.AuthorizationException;
|
|
import org.apache.shiro.authz.UnauthorizedException;
|
|
import org.apache.shiro.config.Ini;
|
|
import org.apache.shiro.config.Ini.Section;
|
|
import org.apache.shiro.crypto.RandomNumberGenerator;
|
|
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
|
|
import org.apache.shiro.crypto.hash.Sha256Hash;
|
|
import org.apache.shiro.mgt.CachingSecurityManager;
|
|
import org.apache.shiro.mgt.SecurityManager;
|
|
import org.apache.shiro.session.Session;
|
|
import org.apache.shiro.subject.Subject;
|
|
import org.apache.shiro.util.Factory;
|
|
import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
|
|
import org.apache.shiro.web.env.IniWebEnvironment;
|
|
import org.apache.shiro.web.filter.mgt.FilterChainManager;
|
|
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
|
|
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
|
|
import org.apache.shiro.web.util.SavedRequest;
|
|
import org.apache.shiro.web.util.WebUtils;
|
|
import org.osgi.util.tracker.ServiceTracker;
|
|
import org.scribe.builder.ServiceBuilder;
|
|
import org.scribe.builder.api.FacebookApi;
|
|
import org.scribe.builder.api.FlickrApi;
|
|
import org.scribe.builder.api.Foursquare2Api;
|
|
import org.scribe.builder.api.GoogleApi;
|
|
import org.scribe.builder.api.ImgUrApi;
|
|
import org.scribe.builder.api.LinkedInApi;
|
|
import org.scribe.builder.api.LiveApi;
|
|
import org.scribe.builder.api.TumblrApi;
|
|
import org.scribe.builder.api.TwitterApi;
|
|
import org.scribe.builder.api.VimeoApi;
|
|
import org.scribe.builder.api.YahooApi;
|
|
import org.scribe.model.Token;
|
|
import org.scribe.oauth.OAuthService;
|
|
|
|
import com.sap.sse.ServerInfo;
|
|
import com.sap.sse.common.Util;
|
|
import com.sap.sse.common.Util.Pair;
|
|
import com.sap.sse.common.mail.MailException;
|
|
import com.sap.sse.mail.MailService;
|
|
import com.sap.sse.replication.OperationExecutionListener;
|
|
import com.sap.sse.replication.OperationWithResult;
|
|
import com.sap.sse.replication.OperationWithResultWithIdWrapper;
|
|
import com.sap.sse.replication.OperationsToMasterSender;
|
|
import com.sap.sse.replication.OperationsToMasterSendingQueue;
|
|
import com.sap.sse.replication.ReplicationMasterDescriptor;
|
|
import com.sap.sse.replication.ReplicationService;
|
|
import com.sap.sse.security.Action;
|
|
import com.sap.sse.security.BearerAuthenticationToken;
|
|
import com.sap.sse.security.ClientUtils;
|
|
import com.sap.sse.security.GithubApi;
|
|
import com.sap.sse.security.InstagramApi;
|
|
import com.sap.sse.security.OAuthRealm;
|
|
import com.sap.sse.security.SecurityService;
|
|
import com.sap.sse.security.SessionCacheManager;
|
|
import com.sap.sse.security.SessionUtils;
|
|
import com.sap.sse.security.interfaces.AccessControlStore;
|
|
import com.sap.sse.security.interfaces.Credential;
|
|
import com.sap.sse.security.interfaces.OAuthToken;
|
|
import com.sap.sse.security.interfaces.Social;
|
|
import com.sap.sse.security.interfaces.SocialSettingsKeys;
|
|
import com.sap.sse.security.interfaces.UserImpl;
|
|
import com.sap.sse.security.interfaces.UserStore;
|
|
import com.sap.sse.security.persistence.PersistenceFactory;
|
|
import com.sap.sse.security.shared.AccessControlListAnnotation;
|
|
import com.sap.sse.security.shared.Account;
|
|
import com.sap.sse.security.shared.Account.AccountType;
|
|
import com.sap.sse.security.shared.AdminRole;
|
|
import com.sap.sse.security.shared.HasPermissions;
|
|
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
|
|
import com.sap.sse.security.shared.HasPermissionsProvider;
|
|
import com.sap.sse.security.shared.OwnershipAnnotation;
|
|
import com.sap.sse.security.shared.PermissionChecker;
|
|
import com.sap.sse.security.shared.PredefinedRoles;
|
|
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
|
|
import com.sap.sse.security.shared.RoleDefinition;
|
|
import com.sap.sse.security.shared.RolePrototype;
|
|
import com.sap.sse.security.shared.SocialUserAccount;
|
|
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
|
|
import com.sap.sse.security.shared.UserGroupManagementException;
|
|
import com.sap.sse.security.shared.UserManagementException;
|
|
import com.sap.sse.security.shared.UserRole;
|
|
import com.sap.sse.security.shared.UsernamePasswordAccount;
|
|
import com.sap.sse.security.shared.WildcardPermission;
|
|
import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
|
|
import com.sap.sse.security.shared.impl.AccessControlList;
|
|
import com.sap.sse.security.shared.impl.Ownership;
|
|
import com.sap.sse.security.shared.impl.PermissionAndRoleAssociation;
|
|
import com.sap.sse.security.shared.impl.Role;
|
|
import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
|
|
import com.sap.sse.security.shared.impl.SecuredSecurityTypes.ServerActions;
|
|
import com.sap.sse.security.shared.impl.User;
|
|
import com.sap.sse.security.shared.impl.UserGroup;
|
|
import com.sap.sse.util.ClearStateTestSupport;
|
|
|
|
public class SecurityServiceImpl implements ReplicableSecurityService, ClearStateTestSupport {
|
|
|
|
private static final Logger logger = Logger.getLogger(SecurityServiceImpl.class.getName());
|
|
|
|
private static final String ADMIN_DEFAULT_PASSWORD = "admin";
|
|
|
|
private final Set<String> migratedHasPermissionTypes = new ConcurrentSkipListSet<>();;
|
|
|
|
private CachingSecurityManager securityManager;
|
|
|
|
/**
|
|
* A cache manager that the {@link SessionCacheManager} delegates to. This way, multiple Shiro configurations can
|
|
* share the cache manager provided as a singleton within this bundle instance. The cache manager is replicating,
|
|
* forwarding changes to the caches to all replicas registered.
|
|
*/
|
|
private final ReplicatingCacheManager cacheManager;
|
|
|
|
private UserStore store;
|
|
private AccessControlStore accessControlStore;
|
|
|
|
private boolean isInitialOrMigration;
|
|
|
|
private final ServiceTracker<MailService, MailService> mailServiceTracker;
|
|
private final ConcurrentMap<OperationExecutionListener<ReplicableSecurityService>, OperationExecutionListener<ReplicableSecurityService>> operationExecutionListeners;
|
|
|
|
/**
|
|
* The master from which this replicable is currently replicating, or <code>null</code> if this replicable is not currently
|
|
* replicated from any master.
|
|
*/
|
|
private ReplicationMasterDescriptor replicatingFromMaster;
|
|
|
|
private Set<OperationWithResultWithIdWrapper<?, ?>> operationsSentToMasterForReplication;
|
|
|
|
private ThreadLocal<Boolean> currentlyFillingFromInitialLoad = ThreadLocal.withInitial(() -> false);
|
|
|
|
private ThreadLocal<Boolean> currentlyApplyingOperationReceivedFromMaster = ThreadLocal.withInitial(() -> false);
|
|
|
|
private ThreadLocal<UserGroup> temporaryDefaultTenant = new InheritableThreadLocal<>();
|
|
|
|
/**
|
|
* This field is expected to be set by the {@link ReplicationService} once it has "adopted" this replicable.
|
|
* The {@link ReplicationService} "injects" this service so it can be used here as a delegate for the
|
|
* {@link OperationsToMasterSendingQueue#scheduleForSending(OperationWithResult, OperationsToMasterSender)}
|
|
* method.
|
|
*/
|
|
private OperationsToMasterSendingQueue unsentOperationsToMasterSender;
|
|
|
|
private static Ini shiroConfiguration;
|
|
|
|
private final HasPermissionsProvider hasPermissionsProvider;
|
|
|
|
private String sharedAcrossSubdomainsOf;
|
|
|
|
private String baseUrlForCrossDomainStorage;
|
|
|
|
static {
|
|
shiroConfiguration = new Ini();
|
|
shiroConfiguration.loadFromPath("classpath:shiro.ini");
|
|
}
|
|
|
|
public SecurityServiceImpl(UserStore userStore, AccessControlStore accessControlStore) {
|
|
this(null, userStore, accessControlStore);
|
|
}
|
|
|
|
/**
|
|
* @param mailProperties
|
|
* must not be <code>null</code>
|
|
*/
|
|
public SecurityServiceImpl(ServiceTracker<MailService, MailService> mailServiceTracker, UserStore userStore, AccessControlStore accessControlStore) {
|
|
this(mailServiceTracker, userStore, accessControlStore, null);
|
|
}
|
|
|
|
/**
|
|
* Creates a security service that is not shared across subdomains, therefore leading to the use of the full
|
|
* domain through which its services are requested for {@code Document.domain} and hence for the browser local
|
|
* storage, session storage and the Shiro {@code JSESSIONID} cookie's domain.
|
|
*
|
|
* @param setAsActivatorSecurityService
|
|
* when <code>true</code>, the {@link Activator#setSecurityService(com.sap.sse.security.SecurityService)}
|
|
* will be called with this new instance as argument so that the cache manager can already be accessed
|
|
* when the security manager is created. {@link ReplicatingCacheManager#getCache(String)} fetches the
|
|
* activator's security service and passes it to the cache entries created. They need it, in turn, for
|
|
* replication.
|
|
*/
|
|
public SecurityServiceImpl(ServiceTracker<MailService, MailService> mailServiceTracker, UserStore userStore,
|
|
AccessControlStore accessControlStore, HasPermissionsProvider hasPermissionsProvider) {
|
|
this(mailServiceTracker, userStore, accessControlStore, hasPermissionsProvider,
|
|
/* sharedAcrossSubdomainsOf */ null, /* baseUrlForCrossDomainStorage */ null);
|
|
}
|
|
|
|
/**
|
|
* Like {@link #SecurityServiceImpl(ServiceTracker, UserStore, AccessControlStore, HasPermissionsProvider)}, only that additionally
|
|
* a parent domain can be specified in {@code isSharedAcrossSubdomains}, such as, e.g., {@code "sapsailing.com"}, across which
|
|
* the browser local and session store shall be shared and for which sessions identified by the {@code JSESSIONID} cookie shall
|
|
* be shared as well.
|
|
*/
|
|
public SecurityServiceImpl(ServiceTracker<MailService, MailService> mailServiceTracker, UserStore userStore,
|
|
AccessControlStore accessControlStore, HasPermissionsProvider hasPermissionsProvider,
|
|
String sharedAcrossSubdomainsOf, String baseUrlForCrossDomainStorage) {
|
|
logger.info("Initializing Security Service with user store " + userStore);
|
|
operationsSentToMasterForReplication = new HashSet<>();
|
|
this.sharedAcrossSubdomainsOf = sharedAcrossSubdomainsOf;
|
|
this.baseUrlForCrossDomainStorage = baseUrlForCrossDomainStorage;
|
|
this.operationExecutionListeners = new ConcurrentHashMap<>();
|
|
this.store = userStore;
|
|
this.accessControlStore = accessControlStore;
|
|
this.mailServiceTracker = mailServiceTracker;
|
|
this.hasPermissionsProvider = hasPermissionsProvider;
|
|
cacheManager = loadReplicationCacheManagerContents();
|
|
Factory<SecurityManager> factory = new WebIniSecurityManagerFactory(shiroConfiguration);
|
|
logger.info("Loaded shiro.ini file from: classpath:shiro.ini");
|
|
StringBuilder logMessage = new StringBuilder("[urls] section from Shiro configuration:");
|
|
final Section urlsSection = shiroConfiguration.getSection("urls");
|
|
if (urlsSection != null) {
|
|
for (Entry<String, String> e : urlsSection.entrySet()) {
|
|
logMessage.append("\n");
|
|
logMessage.append(e.getKey());
|
|
logMessage.append(": ");
|
|
logMessage.append(e.getValue());
|
|
}
|
|
}
|
|
logger.info(logMessage.toString());
|
|
System.setProperty("java.net.useSystemProxies", "true");
|
|
CachingSecurityManager securityManager = (CachingSecurityManager) factory.getInstance();
|
|
logger.info("Created: " + securityManager);
|
|
SecurityUtils.setSecurityManager(securityManager);
|
|
this.securityManager = securityManager;
|
|
}
|
|
|
|
private ReplicatingCacheManager loadReplicationCacheManagerContents() {
|
|
logger.info("Loading session cache manager contents");
|
|
int count = 0;
|
|
final ReplicatingCacheManager result = new ReplicatingCacheManager();
|
|
for (Entry<String, Set<Session>> cacheNameAndSessions : PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory().loadSessionsByCacheName().entrySet()) {
|
|
final String cacheName = cacheNameAndSessions.getKey();
|
|
final ReplicatingCache<Object, Object> cache = (ReplicatingCache<Object, Object>) result.getCache(cacheName,
|
|
this);
|
|
for (final Session session : cacheNameAndSessions.getValue()) {
|
|
cache.put(session.getId(), session, /* store */ false);
|
|
count++;
|
|
}
|
|
}
|
|
logger.info("Loaded "+count+" sessions");
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean isCurrentlyFillingFromInitialLoad() {
|
|
return currentlyFillingFromInitialLoad.get();
|
|
}
|
|
|
|
@Override
|
|
public void setCurrentlyFillingFromInitialLoad(boolean currentlyFillingFromInitialLoad) {
|
|
this.currentlyFillingFromInitialLoad.set(currentlyFillingFromInitialLoad);
|
|
}
|
|
|
|
@Override
|
|
public boolean isCurrentlyApplyingOperationReceivedFromMaster() {
|
|
return currentlyApplyingOperationReceivedFromMaster.get();
|
|
}
|
|
|
|
@Override
|
|
public void setCurrentlyApplyingOperationReceivedFromMaster(boolean currentlyApplyingOperationReceivedFromMaster) {
|
|
this.currentlyApplyingOperationReceivedFromMaster.set(currentlyApplyingOperationReceivedFromMaster);
|
|
}
|
|
|
|
@Override
|
|
public void initialize() {
|
|
initEmptyStore();
|
|
initEmptyAccessControlStore();
|
|
}
|
|
|
|
/**
|
|
* Creates a default "admin" user with initial password "admin" and initial role "admin" if the user <code>store</code>
|
|
* is empty.
|
|
*/
|
|
private void initEmptyStore() {
|
|
final AdminRole adminRolePrototype = AdminRole.getInstance();
|
|
RoleDefinition adminRoleDefinition = getRoleDefinition(adminRolePrototype.getId());
|
|
adminRoleDefinition = getRoleDefinition(adminRolePrototype.getId());
|
|
assert adminRoleDefinition != null;
|
|
try {
|
|
isInitialOrMigration = false;
|
|
if (!store.hasUsers()) {
|
|
isInitialOrMigration = true;
|
|
logger.info("No users found, creating default user \""+UserStore.ADMIN_USERNAME+"\" with password \""+ADMIN_DEFAULT_PASSWORD+"\"");
|
|
final User adminUser = createSimpleUser(UserStore.ADMIN_USERNAME, "nobody@sapsailing.com",
|
|
ADMIN_DEFAULT_PASSWORD,
|
|
/* fullName */ null, /* company */ null, Locale.ENGLISH, /* validationBaseURL */ null,
|
|
null);
|
|
setOwnership(adminUser.getIdentifier(), adminUser, null);
|
|
Role adminRole = new Role(adminRoleDefinition);
|
|
addRoleForUserAndSetUserAsOwner(adminUser, adminRole);
|
|
// add new admin user to server group and make server group the default creation group for the admin user:
|
|
final UserGroup defaultTenant = getServerGroup();
|
|
addUserToUserGroup(defaultTenant, adminUser);
|
|
setDefaultTenantForCurrentServerForUser(adminUser.getName(), defaultTenant.getId());
|
|
}
|
|
if (store.getUserByName(SecurityService.ALL_USERNAME) == null) {
|
|
isInitialOrMigration = true;
|
|
logger.info(SecurityService.ALL_USERNAME + " not found -> creating it now");
|
|
User allUser = apply(s->s.internalCreateUser(SecurityService.ALL_USERNAME, null));
|
|
// <all> user is explicitly not owned by itself because this would enable anybody to modify this user
|
|
setOwnership(allUser.getIdentifier(), null, getServerGroup());
|
|
// The permission to create new users is initially added but not recreated on server start if the admin removed it in the meanwhile.
|
|
// This allows servers to be configured to not permit self-registration of new users but only users being managed by an admin user.
|
|
WildcardPermission createUserPermission = SecuredSecurityTypes.USER.getPermission(DefaultActions.CREATE);
|
|
addPermissionForUser(ALL_USERNAME, createUserPermission);
|
|
QualifiedObjectIdentifier qualifiedTypeIdentifierForPermission = SecuredSecurityTypes.PERMISSION_ASSOCIATION
|
|
.getQualifiedObjectIdentifier(PermissionAndRoleAssociation.get(createUserPermission, allUser));
|
|
// Permission association is owned by the server tenant.
|
|
// This typically ensures that the server admin is able to remove the association.
|
|
setOwnership(qualifiedTypeIdentifierForPermission, null, getServerGroup());
|
|
}
|
|
if (isInitialOrMigration) {
|
|
// predefined roles are meant to be publicly readable
|
|
for (UUID predefinedRoleId : getPredefinedRoleIds()) {
|
|
final RoleDefinition predefinedRole = getRoleDefinition(predefinedRoleId);
|
|
if (predefinedRole != null) {
|
|
addToAccessControlList(predefinedRole.getIdentifier(), null, DefaultActions.READ.name());
|
|
}
|
|
}
|
|
}
|
|
} catch (UserManagementException | MailException | UserGroupManagementException e) {
|
|
logger.log(Level.SEVERE,
|
|
"Exception while creating default " + UserStore.ADMIN_USERNAME + " and " + SecurityService.ALL_USERNAME + " user", e);
|
|
}
|
|
}
|
|
|
|
private Iterable<UUID> getPredefinedRoleIds() {
|
|
final Set<UUID> predefinedRoleIds = new HashSet<>();
|
|
predefinedRoleIds.add(AdminRole.getInstance().getId());
|
|
predefinedRoleIds.add(UserRole.getInstance().getId());
|
|
for (final PredefinedRoles otherPredefinedRole : PredefinedRoles.values()) {
|
|
predefinedRoleIds.add(otherPredefinedRole.getId());
|
|
}
|
|
return predefinedRoleIds;
|
|
}
|
|
|
|
private void initEmptyAccessControlStore() {
|
|
}
|
|
|
|
private MailService getMailService() {
|
|
return mailServiceTracker == null ? null : mailServiceTracker.getService();
|
|
}
|
|
|
|
@Override
|
|
public void sendMail(String username, String subject, String body) throws MailException {
|
|
final User user = getUserByName(username);
|
|
if (user != null) {
|
|
final String toAddress = user.getEmail();
|
|
if (toAddress != null) {
|
|
MailService mailService = getMailService();
|
|
if (mailService == null) {
|
|
logger.warning(String.format("Could not send mail to user %s: no MailService found", username));
|
|
} else {
|
|
getMailService().sendMail(toAddress, subject, body);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void resetPassword(final String username, String passwordResetBaseURL) throws UserManagementException, MailException {
|
|
final User user = store.getUserByName(username);
|
|
if (user == null) {
|
|
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
|
|
}
|
|
if (!user.isEmailValidated()) {
|
|
throw new UserManagementException(UserManagementException.CANNOT_RESET_PASSWORD_WITHOUT_VALIDATED_EMAIL);
|
|
}
|
|
final String passwordResetSecret = user.createRandomSecret();
|
|
apply(s->s.internalResetPassword(username, passwordResetSecret));
|
|
Map<String, String> urlParameters = new HashMap<>();
|
|
try {
|
|
urlParameters.put("u", URLEncoder.encode(user.getName(), "UTF-8"));
|
|
urlParameters.put("e", URLEncoder.encode(user.getEmail(), "UTF-8"));
|
|
urlParameters.put("s", URLEncoder.encode(passwordResetSecret, "UTF-8"));
|
|
final StringBuilder url = buildURL(passwordResetBaseURL, urlParameters);
|
|
new Thread("sending password reset e-mail to user " + username) {
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
sendMail(user.getName(), "Password Reset",
|
|
"Please click on the link below to reset your password for user " + user.getName()
|
|
+ ".\n " + url.toString());
|
|
} catch (MailException e) {
|
|
logger.log(Level.SEVERE, "Error sending mail for password reset of user " + user.getName()
|
|
+ " to address " + user.getEmail(), e);
|
|
}
|
|
}
|
|
}.start();
|
|
} catch (UnsupportedEncodingException e) {
|
|
logger.log(Level.SEVERE,
|
|
"Internal error: encoding UTF-8 not found. Couldn't send e-mail to user " + user.getName()
|
|
+ " at e-mail address " + user.getEmail(), e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Void internalResetPassword(String username, String passwordResetSecret) {
|
|
getUserByName(username).startPasswordReset(passwordResetSecret);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public CachingSecurityManager getSecurityManager() {
|
|
return this.securityManager;
|
|
}
|
|
|
|
@Override
|
|
public OwnershipAnnotation getOwnership(QualifiedObjectIdentifier idOfOwnedObjectAsString) {
|
|
return accessControlStore.getOwnership(idOfOwnedObjectAsString);
|
|
}
|
|
|
|
@Override
|
|
public OwnershipAnnotation createDefaultOwnershipForNewObject(QualifiedObjectIdentifier idOfNewObject) {
|
|
return new OwnershipAnnotation(new Ownership(getCurrentUser(), getDefaultTenantForCurrentUser()),
|
|
idOfNewObject, /* display name */ idOfNewObject.toString());
|
|
}
|
|
|
|
public UserGroup getDefaultTenantForUser(User user) {
|
|
UserGroup specificTenant = temporaryDefaultTenant.get();
|
|
if (specificTenant == null) {
|
|
specificTenant = user.getDefaultTenant(ServerInfo.getName());
|
|
if (specificTenant == null) {
|
|
String defaultTenantName = getDefaultTenantNameForUsername(user.getName());
|
|
specificTenant = getUserGroupByName(defaultTenantName);
|
|
}
|
|
}
|
|
return specificTenant;
|
|
}
|
|
|
|
@Override
|
|
public UserGroup getDefaultTenantForCurrentUser() {
|
|
if (SecurityUtils.getSecurityManager() != null && getCurrentUser() == null) {
|
|
return null;
|
|
}
|
|
return getDefaultTenantForUser(getCurrentUser());
|
|
}
|
|
|
|
@Override
|
|
public RoleDefinition getRoleDefinition(UUID idOfRoleDefinition) {
|
|
return store.getRoleDefinition(idOfRoleDefinition);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all existing access control lists. This is possibly not complete in the sense
|
|
* that there is a access control list for every access controlled data object.
|
|
*/
|
|
@Override
|
|
public Iterable<AccessControlListAnnotation> getAccessControlLists() {
|
|
return accessControlStore.getAccessControlLists();
|
|
}
|
|
|
|
@Override
|
|
public AccessControlListAnnotation getAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObjectAsString) {
|
|
return accessControlStore.getAccessControlList(idOfAccessControlledObjectAsString);
|
|
}
|
|
|
|
@Override
|
|
public SecurityService setEmptyAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObjectAsString) {
|
|
return setEmptyAccessControlList(idOfAccessControlledObjectAsString, /* display name of access-controlled object */ null);
|
|
}
|
|
|
|
@Override
|
|
public SecurityService setEmptyAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObjectAsString, String displayNameOfAccessControlledObject) {
|
|
apply(s->s.internalSetEmptyAccessControlList(idOfAccessControlledObjectAsString, displayNameOfAccessControlledObject));
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Void internalSetEmptyAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject, String displayNameOfAccessControlledObject) {
|
|
accessControlStore.setEmptyAccessControlList(idOfAccessControlledObject, displayNameOfAccessControlledObject);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public AccessControlList overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject,
|
|
Map<UserGroup, Set<String>> permissionMap) {
|
|
accessControlStore.removeAccessControlList(idOfAccessControlledObject);
|
|
return updateAccessControlList(idOfAccessControlledObject, permissionMap);
|
|
}
|
|
|
|
@Override
|
|
public AccessControlList overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject,
|
|
Map<UserGroup, Set<String>> permissionMap, String displayNameOfAccessControlledObject) {
|
|
accessControlStore.removeAccessControlList(idOfAccessControlledObject);
|
|
accessControlStore.setEmptyAccessControlList(idOfAccessControlledObject, displayNameOfAccessControlledObject);
|
|
return updateAccessControlList(idOfAccessControlledObject, permissionMap);
|
|
}
|
|
|
|
@Override
|
|
public AccessControlList updateAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject,
|
|
Map<UserGroup, Set<String>> permissionMap) {
|
|
if (getAccessControlList(idOfAccessControlledObject) == null) {
|
|
setEmptyAccessControlList(idOfAccessControlledObject);
|
|
}
|
|
for (Map.Entry<UserGroup, Set<String>> entry : permissionMap.entrySet()) {
|
|
final UserGroup userGroup = entry.getKey();
|
|
final UUID userGroupId = userGroup == null ? null : userGroup.getId();
|
|
final Set<String> actions = entry.getValue();
|
|
// avoid the UserGroup object having to be serialized with the operation by using the ID
|
|
apply(s -> s.internalAclPutPermissions(idOfAccessControlledObject, userGroupId, actions));
|
|
}
|
|
return accessControlStore.getAccessControlList(idOfAccessControlledObject).getAnnotation();
|
|
}
|
|
|
|
@Override
|
|
public Void internalAclPutPermissions(QualifiedObjectIdentifier idOfAccessControlledObject, UUID groupId, Set<String> actions) {
|
|
accessControlStore.setAclPermissions(idOfAccessControlledObject, getUserGroup(groupId), actions);
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
* @param name The name of the user group to add
|
|
*/
|
|
@Override
|
|
public AccessControlList addToAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject,
|
|
UserGroup group, String action) {
|
|
if (getAccessControlList(idOfAccessControlledObject) == null) {
|
|
setEmptyAccessControlList(idOfAccessControlledObject);
|
|
}
|
|
final UUID groupId = group == null ? null : group.getId();
|
|
apply(s->s.internalAclAddPermission(idOfAccessControlledObject, groupId, action));
|
|
return accessControlStore.getAccessControlList(idOfAccessControlledObject).getAnnotation();
|
|
}
|
|
|
|
@Override
|
|
public Void internalAclAddPermission(QualifiedObjectIdentifier idOfAccessControlledObjectAsString, UUID groupId, String permission) {
|
|
accessControlStore.addAclPermission(idOfAccessControlledObjectAsString, getUserGroup(groupId), permission);
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
* @param name The name of the user group to remove
|
|
*/
|
|
@Override
|
|
public AccessControlList removeFromAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObjectAsString,
|
|
UserGroup group, String permission) {
|
|
final AccessControlList result;
|
|
if (getAccessControlList(idOfAccessControlledObjectAsString) != null) {
|
|
final UUID groupId = group == null ? null : group.getId();
|
|
apply(s->s.internalAclRemovePermission(idOfAccessControlledObjectAsString, groupId, permission));
|
|
result = accessControlStore.getAccessControlList(idOfAccessControlledObjectAsString).getAnnotation();
|
|
} else {
|
|
result = null;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public Void internalAclRemovePermission(QualifiedObjectIdentifier idOfAccessControlledObjectAsString, UUID groupId, String permission) {
|
|
accessControlStore.removeAclPermission(idOfAccessControlledObjectAsString, getUserGroup(groupId), permission);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void deleteAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObjectAsString) {
|
|
if (getAccessControlList(idOfAccessControlledObjectAsString) != null) {
|
|
apply(s->s.internalDeleteAcl(idOfAccessControlledObjectAsString));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Void internalDeleteAcl(QualifiedObjectIdentifier idOfAccessControlledObjectAsString) {
|
|
accessControlStore.removeAccessControlList(idOfAccessControlledObjectAsString);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Ownership setOwnership(QualifiedObjectIdentifier idOfOwnedObjectAsString, User userOwner,
|
|
UserGroup tenantOwner) {
|
|
return setOwnership(idOfOwnedObjectAsString, userOwner, tenantOwner, /* displayNameOfOwnedObject */ null);
|
|
}
|
|
|
|
@Override
|
|
public Ownership setOwnership(QualifiedObjectIdentifier idOfOwnedObjectAsString, User userOwner,
|
|
UserGroup tenantOwner, String displayNameOfOwnedObject) {
|
|
if (userOwner == null && tenantOwner == null) {
|
|
throw new IllegalArgumentException("No owner is not valid, would create non changeable object");
|
|
}
|
|
final UUID tenantId = tenantOwner == null ? null : tenantOwner.getId();
|
|
final String userOwnerName = userOwner == null ? null : userOwner.getName();
|
|
|
|
return apply(s -> s.internalSetOwnership(idOfOwnedObjectAsString, userOwnerName, tenantId,
|
|
displayNameOfOwnedObject));
|
|
}
|
|
|
|
@Override
|
|
public Ownership internalSetOwnership(QualifiedObjectIdentifier idAsString, String userOwnerName, UUID tenantOwnerId, String displayName) {
|
|
return accessControlStore.setOwnership(idAsString, getUserByName(userOwnerName), getUserGroup(tenantOwnerId), displayName).getAnnotation();
|
|
}
|
|
|
|
@Override
|
|
public void deleteOwnership(QualifiedObjectIdentifier idOfOwnedObjectAsString) {
|
|
if (getOwnership(idOfOwnedObjectAsString) != null) {
|
|
apply(s->s.internalDeleteOwnership(idOfOwnedObjectAsString));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Void internalDeleteOwnership(QualifiedObjectIdentifier idOfOwnedObjectAsString) {
|
|
accessControlStore.removeOwnership(idOfOwnedObjectAsString);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Iterable<UserGroup> getUserGroupList() {
|
|
return store.getUserGroups();
|
|
}
|
|
|
|
@Override
|
|
public UserGroup getUserGroup(UUID id) {
|
|
return store.getUserGroup(id);
|
|
}
|
|
|
|
@Override
|
|
public UserGroup getUserGroupByName(String name) {
|
|
return store.getUserGroupByName(name);
|
|
}
|
|
|
|
@Override
|
|
public Iterable<UserGroup> getUserGroupsOfUser(User user) {
|
|
return store.getUserGroupsOfUser(user);
|
|
}
|
|
|
|
@Override
|
|
public UserGroup createUserGroup(UUID id, String name) throws UserGroupManagementException {
|
|
return createUserGroupWithInitialUser(id, name, getCurrentUser());
|
|
}
|
|
|
|
private UserGroup createUserGroupWithInitialUser(UUID id, String name, User initialUser) {
|
|
logger.info("Creating user group " + name + " with ID " + id);
|
|
apply(s -> s.internalCreateUserGroup(id, name));
|
|
final UserGroup userGroup = store.getUserGroup(id);
|
|
if (initialUser != null) {
|
|
logger.info("Adding initial user " + initialUser + " to group " + userGroup);
|
|
addUserToUserGroup(userGroup, initialUser);
|
|
addUserRoleForGroupToUser(userGroup, initialUser);
|
|
}
|
|
return userGroup;
|
|
}
|
|
|
|
@Override
|
|
public Void internalCreateUserGroup(UUID id, String name) throws UserGroupManagementException {
|
|
store.createUserGroup(id, name);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void addUserToUserGroup(UserGroup userGroup, User user) {
|
|
logger.info("Adding user "+user.getName()+" to group "+userGroup.getName());
|
|
userGroup.add(user);
|
|
final UUID groupId = userGroup.getId();
|
|
final String username = user.getName();
|
|
apply(s->s.internalAddUserToUserGroup(groupId, username));
|
|
}
|
|
|
|
@Override
|
|
public Void internalAddUserToUserGroup(UUID groupId, String username) {
|
|
final UserGroup userGroup = getUserGroup(groupId);
|
|
userGroup.add(getUserByName(username));
|
|
store.updateUserGroup(userGroup);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void internalRemoveUserFromUserGroup(UUID groupId, String username) {
|
|
final UserGroup userGroup = getUserGroup(groupId);
|
|
userGroup.remove(getUserByName(username));
|
|
store.updateUserGroup(userGroup);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void removeUserFromUserGroup(UserGroup userGroup, User user) {
|
|
logger.info("Removing user "+user.getName()+" from group "+userGroup.getName());
|
|
userGroup.remove(user);
|
|
final UUID userGroupId = userGroup.getId();
|
|
final String username = user.getName();
|
|
apply(s->s.internalRemoveUserFromUserGroup(userGroupId, username));
|
|
}
|
|
|
|
@Override
|
|
public void putRoleDefinitionToUserGroup(UserGroup userGroup, RoleDefinition roleDefinition, boolean forAll) {
|
|
logger.info("Adding role definition " + roleDefinition.getName() + "(forAll = " + forAll + ") to group "
|
|
+ userGroup.getName());
|
|
apply(s -> s.internalPutRoleDefinitionToUserGroup(userGroup.getId(), roleDefinition.getId(), forAll));
|
|
}
|
|
|
|
@Override
|
|
public Void internalPutRoleDefinitionToUserGroup(UUID groupId, UUID roleDefinitionId, boolean forAll)
|
|
throws UserGroupManagementException {
|
|
final UserGroup userGroup = getUserGroup(groupId);
|
|
userGroup.put(getRoleDefinition(roleDefinitionId), forAll);
|
|
store.updateUserGroup(userGroup);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void removeRoleDefintionFromUserGroup(UserGroup userGroup, RoleDefinition roleDefinition) {
|
|
logger.info("Removing role definition " + roleDefinition.getName() + " from group " + userGroup.getName());
|
|
apply(s -> s.internalRemoveRoleDefinitionFromUserGroup(userGroup.getId(), roleDefinition.getId()));
|
|
}
|
|
|
|
@Override
|
|
public Void internalRemoveRoleDefinitionFromUserGroup(UUID groupId, UUID roleDefinitionId)
|
|
throws UserGroupManagementException {
|
|
final UserGroup userGroup = getUserGroup(groupId);
|
|
userGroup.remove(getRoleDefinition(roleDefinitionId));
|
|
store.updateUserGroup(userGroup);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void deleteUserGroup(UserGroup userGroup) throws UserGroupManagementException {
|
|
logger.info("Removing user group "+userGroup.getName());
|
|
final UUID groupId = userGroup.getId();
|
|
apply(s->s.internalDeleteUserGroup(groupId));
|
|
}
|
|
|
|
@Override
|
|
public Void internalDeleteUserGroup(UUID groupId) throws UserGroupManagementException {
|
|
final UserGroup userGroup = getUserGroup(groupId);
|
|
if (userGroup == null) {
|
|
logger.warning("Strange: the user group with ID "+groupId+" which is about to be deleted couldn't be found");
|
|
} else {
|
|
accessControlStore.removeAllOwnershipsFor(userGroup);
|
|
store.removeAllQualifiedRolesForUserGroup(userGroup);
|
|
store.deleteUserGroup(userGroup);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Iterable<User> getUserList() {
|
|
return store.getUsers();
|
|
}
|
|
|
|
@Override
|
|
public String login(String username, String password) throws AuthenticationException {
|
|
String redirectUrl;
|
|
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
|
|
logger.info("Trying to login: " + username);
|
|
Subject subject = SecurityUtils.getSubject();
|
|
subject.login(token);
|
|
HttpServletRequest httpRequest = WebUtils.getHttpRequest(subject);
|
|
SavedRequest savedRequest = WebUtils.getSavedRequest(httpRequest);
|
|
if (savedRequest != null) {
|
|
redirectUrl = savedRequest.getRequestUrl();
|
|
} else {
|
|
redirectUrl = "";
|
|
}
|
|
logger.info("Redirecturl: " + redirectUrl);
|
|
return redirectUrl;
|
|
}
|
|
|
|
@Override
|
|
public User loginByAccessToken(String accessToken) {
|
|
BearerAuthenticationToken token = new BearerAuthenticationToken(accessToken);
|
|
logger.info("Trying to login with access token");
|
|
Subject subject = SecurityUtils.getSubject();
|
|
try {
|
|
subject.login(token);
|
|
final String username = (String) token.getPrincipal();
|
|
return store.getUserByName(username);
|
|
} catch (AuthenticationException e) {
|
|
logger.log(Level.INFO, "Authentication failed with access token "+accessToken);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void logout() {
|
|
Subject subject = SecurityUtils.getSubject();
|
|
logger.info("Logging out");
|
|
subject.logout();
|
|
}
|
|
|
|
@Override
|
|
public User getUserByName(String name) {
|
|
return store.getUserByName(name);
|
|
}
|
|
|
|
@Override
|
|
public User getUserByAccessToken(String accessToken) {
|
|
return store.getUserByAccessToken(accessToken);
|
|
}
|
|
|
|
@Override
|
|
public User getUserByEmail(String email) {
|
|
return store.getUserByEmail(email);
|
|
}
|
|
|
|
@Override
|
|
public User createSimpleUser(final String username, final String email, String password, String fullName,
|
|
String company, Locale locale, final String validationBaseURL, UserGroup groupOwningUser)
|
|
throws UserManagementException, MailException, UserGroupManagementException {
|
|
logger.info("Creating user "+username);
|
|
if (store.getUserByName(username) != null) {
|
|
logger.warning("User "+username+" already exists");
|
|
throw new UserManagementException(UserManagementException.USER_ALREADY_EXISTS);
|
|
}
|
|
if (username == null || username.length() < 3) {
|
|
throw new UserManagementException(UserManagementException.USERNAME_DOES_NOT_MEET_REQUIREMENTS);
|
|
} else if (password == null || password.length() < 5) {
|
|
throw new UserManagementException(UserManagementException.PASSWORD_DOES_NOT_MEET_REQUIREMENTS);
|
|
}
|
|
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
|
|
byte[] salt = rng.nextBytes().getBytes();
|
|
String hashedPasswordBase64 = hashPassword(password, salt);
|
|
UsernamePasswordAccount upa = new UsernamePasswordAccount(username, hashedPasswordBase64, salt);
|
|
final User result = apply(s->s.internalCreateUser(username, email, upa)); // This also replicated the user creation
|
|
addUserRoleToUser(result);
|
|
final UserGroup tenant = getOrCreateTenantForUser(result);
|
|
setDefaultTenantForCurrentServerForUser(username, tenant.getId());
|
|
// the new user becomes its owner to ensure the user role is working correctly
|
|
// the default tenant is the owning tenant to allow users having admin role for a specific server tenant to also be able to delete users
|
|
apply(s->s.internalSetOwnership(result.getIdentifier(), username, groupOwningUser==null?null:groupOwningUser.getId(), username));
|
|
updateUserProperties(username, fullName, company, locale);
|
|
// email has been set during creation already; the following call will trigger the e-mail validation process
|
|
updateSimpleUserEmail(username, email, validationBaseURL);
|
|
return result;
|
|
}
|
|
|
|
private void addUserRoleToUser(final User user) {
|
|
addRoleForUserAndSetUserAsOwner(user, new Role(store.getRoleDefinitionByPrototype(UserRole.getInstance()),
|
|
/* tenant qualifier */ null, /* user qualifier */ user));
|
|
}
|
|
|
|
private void addUserRoleForGroupToUser(final UserGroup group, final User user) {
|
|
addRoleForUserAndSetUserAsOwner(user, new Role(store.getRoleDefinitionByPrototype(UserRole.getInstance()),
|
|
/* tenant qualifier */ group, /* user qualifier */ null));
|
|
}
|
|
|
|
private UserGroup getOrCreateTenantForUser(User user) throws UserGroupManagementException {
|
|
final String username = user.getName();
|
|
final String defaultTenantNameForUsername = getDefaultTenantNameForUsername(username);
|
|
UserGroup tenant = store.getUserGroupByName(defaultTenantNameForUsername);
|
|
if (tenant != null) {
|
|
logger.info("Found existing tenant "+defaultTenantNameForUsername+" to be used as default tenant for new user "+username);
|
|
} else {
|
|
logger.info("Creating user group "+defaultTenantNameForUsername+" as default tenant for new user "+username);
|
|
tenant = createTenantForUser(defaultTenantNameForUsername, user);
|
|
}
|
|
return tenant;
|
|
}
|
|
|
|
private UserGroup createTenantForUser(String defaultTenantNameForUsername, User user) throws UserGroupManagementException {
|
|
final UserGroup tenant = createUserGroupWithInitialUser(UUID.randomUUID(), defaultTenantNameForUsername, user);
|
|
setOwnership(tenant.getIdentifier(), user, tenant);
|
|
return tenant;
|
|
}
|
|
|
|
@Override
|
|
public User internalCreateUser(String username, String email, Account... accounts) throws UserManagementException {
|
|
final User result = store.createUser(username, email, accounts); // TODO: get the principal as owner
|
|
return result;
|
|
}
|
|
|
|
private String getDefaultTenantNameForUsername(final String username) {
|
|
return username + TENANT_SUFFIX;
|
|
}
|
|
|
|
@Override
|
|
public void updateSimpleUserPassword(String username, String newPassword) throws UserManagementException {
|
|
final User user = store.getUserByName(username);
|
|
if (user == null) {
|
|
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
|
|
}
|
|
updateSimpleUserPassword(user, newPassword);
|
|
}
|
|
|
|
private void updateSimpleUserPassword(final User user, String newPassword) throws UserManagementException {
|
|
if (newPassword == null || newPassword.length() < 5) {
|
|
throw new UserManagementException(UserManagementException.PASSWORD_DOES_NOT_MEET_REQUIREMENTS);
|
|
}
|
|
// for non-admins, check that the old password is correct
|
|
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
|
|
byte[] salt = rng.nextBytes().getBytes();
|
|
String hashedPasswordBase64 = hashPassword(newPassword, salt);
|
|
apply(s->s.internalUpdateSimpleUserPassword(user.getName(), salt, hashedPasswordBase64));
|
|
}
|
|
|
|
@Override
|
|
public Void internalUpdateSimpleUserPassword(String username, byte[] salt, String hashedPasswordBase64) {
|
|
final User user = getUserByName(username);
|
|
final UsernamePasswordAccount account = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD);
|
|
account.setSalt(salt);
|
|
account.setSaltedPassword(hashedPasswordBase64);
|
|
user.passwordWasReset();
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void updateUserProperties(String username, String fullName, String company, Locale locale) throws UserManagementException {
|
|
final User user = store.getUserByName(username);
|
|
if (user == null) {
|
|
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
|
|
}
|
|
apply(s->s.internalUpdateUserProperties(username, fullName, company, locale));
|
|
}
|
|
|
|
@Override
|
|
public Void internalUpdateUserProperties(String username, String fullName, String company, Locale locale) {
|
|
final User user = store.getUserByName(username);
|
|
user.setFullName(fullName);
|
|
user.setCompany(company);
|
|
user.setLocale(locale);
|
|
store.updateUser(user);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public boolean checkPassword(String username, String password) throws UserManagementException {
|
|
final User user = store.getUserByName(username);
|
|
if (user == null) {
|
|
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
|
|
}
|
|
final UsernamePasswordAccount account = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD);
|
|
String hashedOldPassword = hashPassword(password, account.getSalt());
|
|
return Util.equalsWithNull(hashedOldPassword, account.getSaltedPassword());
|
|
}
|
|
|
|
@Override
|
|
public boolean checkPasswordResetSecret(String username, String passwordResetSecret) throws UserManagementException {
|
|
final User user = store.getUserByName(username);
|
|
if (user == null) {
|
|
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
|
|
}
|
|
return Util.equalsWithNull(user.getPasswordResetSecret(), passwordResetSecret);
|
|
}
|
|
|
|
@Override
|
|
public void updateSimpleUserEmail(final String username, final String newEmail, final String validationBaseURL) throws UserManagementException {
|
|
final User user = store.getUserByName(username);
|
|
if (user == null) {
|
|
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
|
|
}
|
|
logger.info("Changing e-mail address of user "+username+" to "+newEmail);
|
|
final String validationSecret = user.createRandomSecret();
|
|
apply(s->s.internalUpdateSimpleUserEmail(username, newEmail, validationSecret));
|
|
if (validationBaseURL != null && newEmail != null && !newEmail.trim().isEmpty()) {
|
|
new Thread("e-mail validation after changing e-mail of user " + username + " to " + newEmail) {
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
startEmailValidation(user, validationSecret, validationBaseURL);
|
|
} catch (MailException e) {
|
|
logger.log(Level.SEVERE, "Error sending mail to validate e-mail address change for user "
|
|
+ username + " to address " + newEmail, e);
|
|
}
|
|
}
|
|
}.start();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Void internalUpdateSimpleUserEmail(final String username, final String newEmail, final String validationSecret) {
|
|
final User user = getUserByName(username);
|
|
user.setEmail(newEmail);
|
|
user.startEmailValidation(validationSecret);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public boolean validateEmail(String username, String validationSecret) throws UserManagementException {
|
|
final User user = store.getUserByName(username);
|
|
if (user == null) {
|
|
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
|
|
}
|
|
return apply(s->s.internalValidateEmail(username, validationSecret));
|
|
}
|
|
|
|
@Override
|
|
public Boolean internalValidateEmail(String username, String validationSecret) {
|
|
final User user = store.getUserByName(username);
|
|
final boolean result = user.validate(validationSecret);
|
|
if (result) {
|
|
store.updateUser(user);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* {@link UserImpl#startEmailValidation(String) Triggers} e-mail validation for the <code>user</code> object and sends out a
|
|
* URL to the user's e-mail that has the validation secret ready for validation by clicking.
|
|
*
|
|
* @param validationSecret
|
|
* the result of either {@link UserImpl#startEmailValidation(String)} or {@link UserImpl#setEmail(String)}.
|
|
* @param baseURL
|
|
* the URL under which the user can reach the e-mail validation service; this URL is required to assemble
|
|
* a validation URL that is sent by e-mail to the user, to make the user return the validation secret to
|
|
* the right server again.
|
|
*/
|
|
private void startEmailValidation(User user, String validationSecret, String baseURL) throws MailException {
|
|
try {
|
|
Map<String, String> urlParameters = new HashMap<>();
|
|
urlParameters.put("u", URLEncoder.encode(user.getName(), "UTF-8"));
|
|
urlParameters.put("v", URLEncoder.encode(validationSecret, "UTF-8"));
|
|
StringBuilder url = buildURL(baseURL, urlParameters);
|
|
sendMail(user.getName(), "e-Mail Validation",
|
|
"Please click on the link below to validate your e-mail address for user "+user.getName()+".\n "+url.toString());
|
|
} catch (UnsupportedEncodingException e) {
|
|
logger.log(Level.SEVERE,
|
|
"Internal error: encoding UTF-8 not found. Couldn't send e-mail to user " + user.getName()
|
|
+ " at e-mail address " + user.getEmail(), e);
|
|
}
|
|
}
|
|
|
|
public StringBuilder buildURL(String baseURL, Map<String, String> urlParameters) {
|
|
StringBuilder url = new StringBuilder(baseURL==null?"":baseURL);
|
|
// Potentially contained hash is checked to support place-based mail verification
|
|
boolean first = baseURL == null || !baseURL.contains("?") || baseURL.contains("#");
|
|
for (Map.Entry<String, String> e : urlParameters.entrySet()) {
|
|
if (first) {
|
|
url.append('?');
|
|
first = false;
|
|
} else {
|
|
url.append('&');
|
|
}
|
|
url.append(e.getKey());
|
|
url.append('=');
|
|
url.append(e.getValue());
|
|
}
|
|
return url;
|
|
}
|
|
|
|
protected String hashPassword(String password, Object salt) {
|
|
return new Sha256Hash(password, salt, 1024).toBase64();
|
|
}
|
|
|
|
@Override
|
|
public RoleDefinition createRoleDefinition(UUID roleId, String name) {
|
|
return apply(s->s.internalCreateRoleDefinition(roleId, name));
|
|
}
|
|
|
|
@Override
|
|
public RoleDefinition internalCreateRoleDefinition(UUID roleId, String name) {
|
|
return store.createRoleDefinition(roleId, name, Collections.emptySet());
|
|
}
|
|
|
|
@Override
|
|
public void deleteRoleDefinition(RoleDefinition roleDefinition) {
|
|
final UUID roleId = roleDefinition.getId();
|
|
apply(s->s.internalDeleteRoleDefinition(roleId));
|
|
}
|
|
|
|
@Override
|
|
public Void internalDeleteRoleDefinition(UUID roleId) {
|
|
final RoleDefinition role = store.getRoleDefinition(roleId);
|
|
store.removeRoleDefinition(role);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void updateRoleDefinition(RoleDefinition roleDefinitionWithNewProperties) {
|
|
apply(s->s.internalUpdateRoleDefinition(roleDefinitionWithNewProperties));
|
|
}
|
|
|
|
@Override
|
|
public Void internalUpdateRoleDefinition(RoleDefinition roleWithNewProperties) {
|
|
final RoleDefinition role = store.getRoleDefinition(roleWithNewProperties.getId());
|
|
role.setName(roleWithNewProperties.getName());
|
|
store.setRoleDefinitionDisplayName(roleWithNewProperties.getId(), role.getName());
|
|
role.setPermissions(roleWithNewProperties.getPermissions());
|
|
store.setRoleDefinitionPermissions(role.getId(), role.getPermissions());
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Iterable<RoleDefinition> getRoleDefinitions() {
|
|
Collection<RoleDefinition> result = new ArrayList<>();
|
|
filterObjectsWithPermissionForCurrentUser(DefaultActions.READ,
|
|
store.getRoleDefinitions(), t -> result.add(t));
|
|
return result;
|
|
}
|
|
|
|
private void addRoleForUserAndSetUserAsOwner(User user, Role role) {
|
|
addRoleForUser(user.getName(), role);
|
|
TypeRelativeObjectIdentifier associationTypeIdentifier = PermissionAndRoleAssociation.get(role, user);
|
|
QualifiedObjectIdentifier qualifiedTypeIdentifier = SecuredSecurityTypes.ROLE_ASSOCIATION
|
|
.getQualifiedObjectIdentifier(associationTypeIdentifier);
|
|
setOwnership(qualifiedTypeIdentifier, user, null);
|
|
}
|
|
|
|
@Override
|
|
public void addRoleForUser(User user, Role role) {
|
|
addRoleForUser(user.getName(), role);
|
|
}
|
|
|
|
@Override
|
|
public void addRoleForUser(String username, Role role) {
|
|
final UUID roleDefinitionId = role.getRoleDefinition().getId();
|
|
final UUID idOfTenantQualifyingRole = role.getQualifiedForTenant() == null ? null : role.getQualifiedForTenant().getId();
|
|
final String nameOfUserQualifyingRole = role.getQualifiedForUser() == null ? null : role.getQualifiedForUser().getName();
|
|
apply(s->s.internalAddRoleForUser(username, roleDefinitionId, idOfTenantQualifyingRole, nameOfUserQualifyingRole));
|
|
}
|
|
|
|
@Override
|
|
public Void internalAddRoleForUser(String username, UUID roleDefinitionId, UUID idOfTenantQualifyingRole,
|
|
String nameOfUserQualifyingRole) throws UserManagementException {
|
|
store.addRoleForUser(username, new Role(getRoleDefinition(roleDefinitionId),
|
|
getUserGroup(idOfTenantQualifyingRole), getUserByName(nameOfUserQualifyingRole)));
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void removeRoleFromUser(User user, Role role) {
|
|
removeRoleFromUser(user.getName(), role);
|
|
}
|
|
|
|
@Override
|
|
public void removeRoleFromUser(String username, Role role) {
|
|
final UUID roleDefinitionId = role.getRoleDefinition().getId();
|
|
final UUID idOfTenantQualifyingRole = role.getQualifiedForTenant() == null ? null : role.getQualifiedForTenant().getId();
|
|
final String nameOfUserQualifyingRole = role.getQualifiedForUser() == null ? null : role.getQualifiedForUser().getName();
|
|
apply(s->s.internalRemoveRoleFromUser(username, roleDefinitionId, idOfTenantQualifyingRole, nameOfUserQualifyingRole));
|
|
}
|
|
|
|
@Override
|
|
public Void internalRemoveRoleFromUser(String username, UUID roleDefinitionId, UUID idOfTenantQualifyingRole,
|
|
String nameOfUserQualifyingRole) throws UserManagementException {
|
|
store.removeRoleFromUser(username, new Role(getRoleDefinition(roleDefinitionId),
|
|
getUserGroup(idOfTenantQualifyingRole), getUserByName(nameOfUserQualifyingRole)));
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Iterable<WildcardPermission> getPermissionsFromUser(String username) throws UserManagementException {
|
|
return store.getPermissionsFromUser(username);
|
|
}
|
|
|
|
@Override
|
|
public void removePermissionFromUser(String username, WildcardPermission permissionToRemove) {
|
|
apply(s->s.internalRemovePermissionForUser(username, permissionToRemove));
|
|
}
|
|
|
|
@Override
|
|
public Void internalRemovePermissionForUser(String username, WildcardPermission permissionToRemove) throws UserManagementException {
|
|
store.removePermissionFromUser(username, permissionToRemove);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void addPermissionForUser(String username, WildcardPermission permissionToAdd) {
|
|
apply(s->s.internalAddPermissionForUser(username, permissionToAdd));
|
|
}
|
|
|
|
@Override
|
|
public Void internalAddPermissionForUser(String username, WildcardPermission permissionToAdd) throws UserManagementException {
|
|
store.addPermissionForUser(username, permissionToAdd);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void deleteUser(String username) throws UserManagementException {
|
|
final User userToDelete = store.getUserByName(username);
|
|
if (userToDelete == null) {
|
|
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
|
|
}
|
|
apply(s -> s.internalDeleteUser(username));
|
|
}
|
|
|
|
@Override
|
|
public Void internalDeleteUser(String username) throws UserManagementException {
|
|
User userToDelete = store.getUserByName(username);
|
|
if (userToDelete != null) {
|
|
// remove all permissions the user has
|
|
accessControlStore.removeAllOwnershipsFor(userToDelete);
|
|
store.removeAllQualifiedRolesForUser(userToDelete);
|
|
final String defaultTenantNameForUsername = getDefaultTenantNameForUsername(username);
|
|
final UserGroup defaultTenantUserGroup = getUserGroupByName(defaultTenantNameForUsername);
|
|
if (defaultTenantUserGroup != null) {
|
|
List<User> usersInGroupList = Util.asList(defaultTenantUserGroup.getUsers());
|
|
if (usersInGroupList.size() == 1 && usersInGroupList.contains(userToDelete)) {
|
|
// no other user is in group, delete it as well
|
|
try {
|
|
internalDeleteUserGroup(defaultTenantUserGroup.getId());
|
|
} catch (UserGroupManagementException e) {
|
|
logger.log(Level.SEVERE, "Could not delete default tenant for user", e);
|
|
}
|
|
}
|
|
}
|
|
// also remove from all usergroups
|
|
final Iterable<UserGroup> userGroups = userToDelete.getUserGroups();
|
|
final Collection<UserGroup> userGroupsToLoopOver = new ArrayList<>();
|
|
Util.addAll(userGroups, userGroupsToLoopOver); // avoid concurrent modification exception
|
|
for (UserGroup userGroup : userGroupsToLoopOver) {
|
|
if (userGroup != defaultTenantUserGroup) { // the defaultTenantUserGroup has already been deleted above
|
|
internalRemoveUserFromUserGroup(userGroup.getId(), userToDelete.getName());
|
|
}
|
|
}
|
|
store.deleteUser(username);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public boolean setSetting(String key, Object setting) {
|
|
return apply(s->s.internalSetSetting(key, setting));
|
|
}
|
|
|
|
@Override
|
|
public Boolean internalSetSetting(String key, Object setting) {
|
|
return store.setSetting(key, setting);
|
|
}
|
|
|
|
@Override
|
|
public <T> T getSetting(String key, Class<T> clazz) {
|
|
return store.getSetting(key, clazz);
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Object> getAllSettings() {
|
|
return store.getAllSettings();
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Class<?>> getAllSettingTypes() {
|
|
return store.getAllSettingTypes();
|
|
}
|
|
|
|
@Override
|
|
public User createSocialUser(String name, SocialUserAccount socialUserAccount)
|
|
throws UserManagementException, UserGroupManagementException {
|
|
if (store.getUserByName(name) != null) {
|
|
throw new UserManagementException(UserManagementException.USER_ALREADY_EXISTS);
|
|
}
|
|
User result = store.createUser(name, socialUserAccount.getProperty(Social.EMAIL.name()),
|
|
socialUserAccount);
|
|
UserGroup tenant = getOrCreateTenantForUser(result);
|
|
accessControlStore.setOwnership(tenant.getIdentifier(), result, tenant, tenant.getName());
|
|
addUserToUserGroup(tenant, result);
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public User verifySocialUser(Credential credential) throws UserManagementException {
|
|
OAuthToken otoken = new OAuthToken(credential, credential.getVerifier());
|
|
Subject subject = SecurityUtils.getSubject();
|
|
if (!subject.isAuthenticated()) {
|
|
try {
|
|
subject.login(otoken);
|
|
logger.info("User [" + subject.getPrincipal().toString() + "] logged in successfully.");
|
|
} catch (UnknownAccountException uae) {
|
|
logger.info("There is no user with username of " + subject.getPrincipal());
|
|
throw new UserManagementException("Invalid credentials!");
|
|
} catch (IncorrectCredentialsException ice) {
|
|
logger.info("Password for account " + subject.getPrincipal() + " was incorrect!");
|
|
throw new UserManagementException("Invalid credentials!");
|
|
} catch (LockedAccountException lae) {
|
|
logger.info("The account for username " + subject.getPrincipal() + " is locked. "
|
|
+ "Please contact your administrator to unlock it.");
|
|
throw new UserManagementException("Invalid credentials!");
|
|
} catch (AuthenticationException ae) {
|
|
logger.log(Level.SEVERE, ae.getLocalizedMessage());
|
|
throw new UserManagementException("An error occured while authenticating the user!");
|
|
}
|
|
}
|
|
String username = subject.getPrincipal().toString();
|
|
if (username == null) {
|
|
logger.info("Something went wrong while authneticating, check doGetAuthenticationInfo() in "
|
|
+ OAuthRealm.class.getName() + ".");
|
|
throw new UserManagementException("An error occured while authenticating the user!");
|
|
}
|
|
User user = store.getUserByName(username);
|
|
if (user == null) {
|
|
logger.info("Could not find user " + username);
|
|
throw new UserManagementException("An error occured while authenticating the user!");
|
|
}
|
|
return user;
|
|
}
|
|
|
|
@Override
|
|
public User getCurrentUser() {
|
|
final User result;
|
|
Subject subject = SecurityUtils.getSubject();
|
|
if (subject == null || !subject.isAuthenticated()) {
|
|
result = null;
|
|
} else {
|
|
Object principal = subject.getPrincipal();
|
|
if (principal == null) {
|
|
result = null;
|
|
} else {
|
|
String username = principal.toString();
|
|
if (username == null || username.length() <= 0) {
|
|
result = null;
|
|
} else {
|
|
result = store.getUserByName(username);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public String getAuthenticationUrl(Credential credential) throws UserManagementException {
|
|
Token requestToken = null;
|
|
String authorizationUrl = null;
|
|
int authProvider = credential.getAuthProvider();
|
|
OAuthService service = getOAuthService(authProvider);
|
|
if (service == null) {
|
|
throw new UserManagementException("Could not build OAuthService");
|
|
}
|
|
if (authProvider == ClientUtils.TWITTER || authProvider == ClientUtils.YAHOO
|
|
|| authProvider == ClientUtils.LINKEDIN || authProvider == ClientUtils.FLICKR
|
|
|| authProvider == ClientUtils.IMGUR || authProvider == ClientUtils.TUMBLR
|
|
|| authProvider == ClientUtils.VIMEO || authProvider == ClientUtils.GOOGLE) {
|
|
String authProviderName = ClientUtils.getAuthProviderName(authProvider);
|
|
logger.info(authProviderName + " requires Request token first.. obtaining..");
|
|
try {
|
|
requestToken = service.getRequestToken();
|
|
logger.info("Got request token: " + requestToken);
|
|
// we must save in the session. It will be required to
|
|
// get the access token
|
|
SessionUtils.saveRequestTokenToSession(requestToken);
|
|
} catch (Exception e) {
|
|
throw new UserManagementException("Could not get request token for " + authProvider + " "
|
|
+ e.getMessage());
|
|
}
|
|
}
|
|
logger.info("Getting Authorization url...");
|
|
try {
|
|
authorizationUrl = service.getAuthorizationUrl(requestToken);
|
|
// Facebook has optional state var to protect against CSFR.
|
|
// We'll use it
|
|
if (authProvider == ClientUtils.FACEBOOK || authProvider == ClientUtils.GITHUB
|
|
|| authProvider == ClientUtils.INSTAGRAM) {
|
|
String state = UUID.randomUUID().toString();
|
|
authorizationUrl += "&state=" + state;
|
|
SessionUtils.saveStateToSession(state);
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
throw new UserManagementException("Could not get Authorization url: ");
|
|
}
|
|
|
|
if (authProvider == ClientUtils.FLICKR) {
|
|
authorizationUrl += "&perms=read";
|
|
}
|
|
|
|
if (authProvider == ClientUtils.FACEBOOK) {
|
|
authorizationUrl += "&scope=email";
|
|
}
|
|
|
|
logger.info("Authorization url: " + authorizationUrl);
|
|
return authorizationUrl;
|
|
}
|
|
|
|
private OAuthService getOAuthService(int authProvider) {
|
|
OAuthService service = null;
|
|
switch (authProvider) {
|
|
case ClientUtils.FACEBOOK: {
|
|
service = new ServiceBuilder().provider(FacebookApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_FACEBOOK_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_FACEBOOK_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
}
|
|
|
|
case ClientUtils.GOOGLE: {
|
|
service = new ServiceBuilder().provider(GoogleApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_GOOGLE_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_GOOGLE_APP_SECRET.name(), String.class))
|
|
.scope(store.getSetting(SocialSettingsKeys.OAUTH_GOOGLE_SCOPE.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
|
|
break;
|
|
}
|
|
|
|
case ClientUtils.TWITTER: {
|
|
service = new ServiceBuilder().provider(TwitterApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_TWITTER_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_TWITTER_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
}
|
|
case ClientUtils.YAHOO: {
|
|
service = new ServiceBuilder().provider(YahooApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_YAHOO_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_YAHOO_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
}
|
|
|
|
case ClientUtils.LINKEDIN: {
|
|
service = new ServiceBuilder().provider(LinkedInApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_LINKEDIN_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_LINKEDIN_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
}
|
|
|
|
case ClientUtils.INSTAGRAM: {
|
|
service = new ServiceBuilder().provider(InstagramApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_INSTAGRAM_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_INSTAGRAM_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
}
|
|
|
|
case ClientUtils.GITHUB: {
|
|
service = new ServiceBuilder().provider(GithubApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_GITHUB_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_GITHUB_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
|
|
}
|
|
|
|
case ClientUtils.IMGUR: {
|
|
service = new ServiceBuilder().provider(ImgUrApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_IMGUR_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_IMGUR_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
}
|
|
|
|
case ClientUtils.FLICKR: {
|
|
service = new ServiceBuilder().provider(FlickrApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_FLICKR_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_FLICKR_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
}
|
|
|
|
case ClientUtils.VIMEO: {
|
|
service = new ServiceBuilder().provider(VimeoApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_VIMEO_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_VIMEO_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
}
|
|
|
|
case ClientUtils.WINDOWS_LIVE: {
|
|
// a Scope must be specified
|
|
service = new ServiceBuilder().provider(LiveApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_WINDOWS_LIVE_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_WINDOWS_LIVE_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).scope("wl.basic").build();
|
|
break;
|
|
}
|
|
|
|
case ClientUtils.TUMBLR: {
|
|
service = new ServiceBuilder().provider(TumblrApi.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_TUMBLR_LIVE_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_TUMBLR_LIVE_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
}
|
|
|
|
case ClientUtils.FOURSQUARE: {
|
|
service = new ServiceBuilder().provider(Foursquare2Api.class)
|
|
.apiKey(store.getSetting(SocialSettingsKeys.OAUTH_FOURSQUARE_APP_ID.name(), String.class))
|
|
.apiSecret(store.getSetting(SocialSettingsKeys.OAUTH_FOURSQUARE_APP_SECRET.name(), String.class))
|
|
.callback(ClientUtils.getCallbackUrl()).build();
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
return null;
|
|
}
|
|
|
|
}
|
|
return service;
|
|
}
|
|
|
|
@Override
|
|
public void addSetting(String key, Class<?> clazz) throws UserManagementException {
|
|
if (!isValidSettingsKey(key)) {
|
|
throw new UserManagementException("Invalid key!");
|
|
}
|
|
apply(s->s.internalAddSetting(key, clazz));
|
|
}
|
|
|
|
@Override
|
|
public Void internalAddSetting(String key, Class<?> clazz) {
|
|
store.addSetting(key, clazz);
|
|
return null;
|
|
}
|
|
|
|
public static boolean isValidSettingsKey(String key) {
|
|
char[] characters = key.toCharArray();
|
|
for (char c : characters) {
|
|
if (!Character.isLetter(c) && c != '_') {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void refreshSecurityConfig(ServletContext context) {
|
|
logger.info("Refreshing security configuration!");
|
|
IniWebEnvironment env = (IniWebEnvironment) WebUtils.getRequiredWebEnvironment(context);
|
|
System.out.println("Env: " + env);
|
|
FilterChainResolver resolver = env.getFilterChainResolver();
|
|
System.out.println("Resolver: " + resolver);
|
|
if (resolver instanceof PathMatchingFilterChainResolver) {
|
|
PathMatchingFilterChainResolver pmfcr = (PathMatchingFilterChainResolver) resolver;
|
|
FilterChainManager filterChainManager = pmfcr.getFilterChainManager();
|
|
System.out.println(filterChainManager);
|
|
|
|
Set<String> chainNames = filterChainManager.getChainNames();
|
|
|
|
System.out.println("Chains:");
|
|
for (String s : chainNames) {
|
|
System.out.println(s + ": " + Arrays.toString(filterChainManager.getChain(s).toArray(new Filter[0])));
|
|
}
|
|
}
|
|
}
|
|
|
|
public static Ini getShiroConfiguration() {
|
|
return shiroConfiguration;
|
|
}
|
|
|
|
@Override
|
|
public ReplicatingCacheManager getCacheManager() {
|
|
return cacheManager;
|
|
}
|
|
|
|
@Override
|
|
public void setPreference(final String username, final String key, final String value) {
|
|
apply(s->s.internalSetPreference(username, key, value));
|
|
}
|
|
|
|
@Override
|
|
public void setPreferenceObject(final String username, final String key, final Object value) {
|
|
final String preferenceObjectAsString = internalSetPreferenceObject(username, key, value);
|
|
apply(s->s.internalSetPreference(username, key, preferenceObjectAsString));
|
|
}
|
|
|
|
@Override
|
|
public Void internalSetPreference(final String username, final String key, final String value) {
|
|
store.setPreference(username, key, value);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public String internalSetPreferenceObject(final String username, final String key, final Object value) {
|
|
return store.setPreferenceObject(username, key, value);
|
|
}
|
|
|
|
@Override
|
|
public void unsetPreference(String username, String key) {
|
|
apply(s->s.internalUnsetPreference(username, key));
|
|
}
|
|
|
|
@Override
|
|
public Void internalUnsetPreference(String username, String key) {
|
|
store.unsetPreference(username, key);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void internalSetAccessToken(String username, String accessToken) {
|
|
store.setAccessToken(username, accessToken);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public String getAccessToken(String username) {
|
|
return store.getAccessToken(username);
|
|
}
|
|
|
|
@Override
|
|
public String getOrCreateAccessToken(String username) {
|
|
String result = store.getAccessToken(username);
|
|
if (result == null) {
|
|
result = createAccessToken(username);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public Void internalRemoveAccessToken(String username) {
|
|
store.removeAccessToken(username);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public String getPreference(String username, String key) {
|
|
return store.getPreference(username, key);
|
|
}
|
|
|
|
@Override
|
|
public Map<String, String> getAllPreferences(String username) {
|
|
return store.getAllPreferences(username);
|
|
}
|
|
|
|
@Override
|
|
public String createAccessToken(String username) {
|
|
User user = getUserByName(username);
|
|
final String token;
|
|
if (user != null) {
|
|
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
|
|
byte[] salt = rng.nextBytes().getBytes();
|
|
token = hashPassword(new String(rng.nextBytes().getBytes()), salt);
|
|
apply(s -> s.internalSetAccessToken(user.getName(), token));
|
|
} else {
|
|
token = null;
|
|
}
|
|
return token;
|
|
}
|
|
|
|
@Override
|
|
public void removeAccessToken(String username) {
|
|
Subject subject = SecurityUtils.getSubject();
|
|
if (hasCurrentUserUpdatePermission(getUserByName(username))) {
|
|
apply(s -> s.internalRemoveAccessToken(username));
|
|
} else {
|
|
throw new org.apache.shiro.authz.AuthorizationException("User " + subject.getPrincipal().toString()
|
|
+ " does not have permission to remove access token of user " + username);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public UserGroup getServerGroup() {
|
|
return store.getServerGroup();
|
|
}
|
|
|
|
@Override
|
|
public <T> T setOwnershipCheckPermissionForObjectCreationAndRevertOnError(
|
|
HasPermissions type, TypeRelativeObjectIdentifier typeIdentifier, String securityDisplayName,
|
|
Callable<T> actionWithResult) {
|
|
return setOwnershipCheckPermissionForObjectCreationAndRevertOnError(type, typeIdentifier,
|
|
securityDisplayName, actionWithResult, true);
|
|
}
|
|
|
|
/**
|
|
* For the current session's {@link Subject} an ownership for an object of type {@code type} and with
|
|
* type-relative object identifier {@code typeRelativeIdentifier} is created with the subject's
|
|
* default creation group if no ownership for that object is found yet. Otherwise, the existing ownership
|
|
* is left untouched.<p>
|
|
*
|
|
* If {@code doServerCreateObjectCheck} is {@code true}, the {@link ServerActions#CREATE_OBJECT} permission
|
|
* is checked for the executing server.<p>
|
|
*
|
|
* Then, the {@link DefaultActions#CREATE} permission is checked for the {@code type}/{@code typeRelativeIdentifier}
|
|
* object.<p>
|
|
*
|
|
* If any of these permission checks fails and the ownership has not been found but created, the ownership
|
|
* is removed again, and the authorization exception is thrown by this method. Otherwise, the subject is
|
|
* considered to have the permission to create the object, the {@code actionWithResult} is invoked, and
|
|
* the object returned by the action is the result of this method.
|
|
*/
|
|
private <T> T setOwnershipCheckPermissionForObjectCreationAndRevertOnError(HasPermissions type,
|
|
TypeRelativeObjectIdentifier typeRelativeIdentifier, String securityDisplayName, Callable<T> actionWithResult,
|
|
boolean doServerCreateObjectCheck) {
|
|
QualifiedObjectIdentifier identifier = type.getQualifiedObjectIdentifier(typeRelativeIdentifier);
|
|
T result = null;
|
|
boolean didSetOwnership = false;
|
|
try {
|
|
final OwnershipAnnotation preexistingOwnership = getOwnership(identifier);
|
|
if (preexistingOwnership == null) {
|
|
didSetOwnership = true;
|
|
setDefaultOwnership(identifier, securityDisplayName);
|
|
} else {
|
|
logger.fine("Preexisting ownership found for " + identifier + ": " + preexistingOwnership);
|
|
}
|
|
if (doServerCreateObjectCheck) {
|
|
checkCurrentUserServerPermission(ServerActions.CREATE_OBJECT);
|
|
}
|
|
try {
|
|
SecurityUtils.getSubject().checkPermission(identifier.getStringPermission(DefaultActions.CREATE));
|
|
} catch (AuthorizationException e) {
|
|
if (didSetOwnership) {
|
|
throw e;
|
|
} else {
|
|
// An ownership for this ID already exists and the user is not permitted
|
|
// -> a nicer error is produced to explain the user that a name clash is most probably the cause
|
|
throw new UnauthorizedException(MessageFormat.format(
|
|
"You are not permitted to create a \"{0}\" with name or identifier \"{1}\". "
|
|
+ "This is most probably caused by an already existing entry with the same name/identifier. "
|
|
+ "Please try to use a different name.",
|
|
identifier.getTypeIdentifier(), identifier.getTypeRelativeObjectIdentifier().toString()), e);
|
|
}
|
|
}
|
|
result = actionWithResult.call();
|
|
} catch (AuthorizationException e) {
|
|
if (didSetOwnership) {
|
|
deleteOwnership(identifier);
|
|
}
|
|
throw e;
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean hasCurrentUserServerPermission(ServerActions action) {
|
|
return SecurityUtils.getSubject().isPermitted(constructServerPermissionString(action));
|
|
}
|
|
|
|
@Override
|
|
public void checkCurrentUserServerPermission(ServerActions action) {
|
|
SecurityUtils.getSubject().checkPermission(constructServerPermissionString(action));
|
|
}
|
|
|
|
private String constructServerPermissionString(ServerActions action) {
|
|
return SecuredSecurityTypes.SERVER.getStringPermissionForTypeRelativeIdentifier(
|
|
action, new TypeRelativeObjectIdentifier(ServerInfo.getName()));
|
|
}
|
|
|
|
@Override
|
|
public <T> T setOwnershipWithoutCheckPermissionForObjectCreationAndRevertOnError(HasPermissions type,
|
|
TypeRelativeObjectIdentifier typeIdentifier, String securityDisplayName, Callable<T> actionWithResult) {
|
|
return setOwnershipCheckPermissionForObjectCreationAndRevertOnError(type, typeIdentifier, securityDisplayName,
|
|
actionWithResult, false);
|
|
}
|
|
|
|
@Override
|
|
public void setDefaultOwnership(QualifiedObjectIdentifier identifier, String description) {
|
|
setOwnership(identifier, getCurrentUser(), getDefaultTenantForCurrentUser(), description);
|
|
}
|
|
|
|
@Override
|
|
public void setDefaultOwnershipIfNotSet(QualifiedObjectIdentifier identifier) {
|
|
final OwnershipAnnotation preexistingOwnership = getOwnership(identifier);
|
|
if (preexistingOwnership == null) {
|
|
setDefaultOwnership(identifier, identifier.toString());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setOwnershipCheckPermissionForObjectCreationAndRevertOnError(HasPermissions type,
|
|
TypeRelativeObjectIdentifier typeRelativeObjectIdentifier, String securityDisplayName,
|
|
Action actionToCreateObject) {
|
|
setOwnershipCheckPermissionForObjectCreationAndRevertOnError(type, typeRelativeObjectIdentifier,
|
|
securityDisplayName, () -> {
|
|
actionToCreateObject.run();
|
|
return null;
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void setOwnershipWithoutCheckPermissionForObjectCreationAndRevertOnError(HasPermissions type,
|
|
TypeRelativeObjectIdentifier typeRelativeObjectIdentifier, String securityDisplayName,
|
|
Action actionToCreateObject) {
|
|
setOwnershipWithoutCheckPermissionForObjectCreationAndRevertOnError(type, typeRelativeObjectIdentifier,
|
|
securityDisplayName, () -> {
|
|
actionToCreateObject.run();
|
|
return null;
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void setOwnershipIfNotSet(QualifiedObjectIdentifier identifier, User user, UserGroup tenantOwner) {
|
|
final OwnershipAnnotation preexistingOwnership = getOwnership(identifier);
|
|
if (preexistingOwnership == null) {
|
|
setOwnership(identifier, user, tenantOwner, identifier.toString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Special case for user creation, as no currentUser might exist when registering anonymous, and since a user always
|
|
* should own itself as userOwner
|
|
*
|
|
* @return
|
|
*/
|
|
@Override
|
|
public User checkPermissionForObjectCreationAndRevertOnErrorForUserCreation(String username,
|
|
Callable<User> createActionReturningCreatedObject) {
|
|
QualifiedObjectIdentifier identifier = SecuredSecurityTypes.USER
|
|
.getQualifiedObjectIdentifier(UserImpl.getTypeRelativeObjectIdentifier(username));
|
|
User result = null;
|
|
try {
|
|
SecurityUtils.getSubject().checkPermission(identifier.getStringPermission(DefaultActions.CREATE));
|
|
result = createActionReturningCreatedObject.call();
|
|
} catch (AuthorizationException e) {
|
|
throw e;
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public void checkPermissionAndDeleteOwnershipForObjectRemoval(WithQualifiedObjectIdentifier object,
|
|
Action actionToDeleteObject) {
|
|
checkPermissionAndDeleteOwnershipForObjectRemoval(object, () -> {
|
|
actionToDeleteObject.run();
|
|
return null;
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public <T> T checkPermissionAndDeleteOwnershipForObjectRemoval(WithQualifiedObjectIdentifier object,
|
|
Callable<T> actionToDeleteObject) {
|
|
QualifiedObjectIdentifier identifier = object.getIdentifier();
|
|
return checkPermissionAndDeleteOwnershipForObjectRemoval(identifier, actionToDeleteObject);
|
|
}
|
|
|
|
@Override
|
|
public <T> T checkPermissionAndDeleteOwnershipForObjectRemoval(QualifiedObjectIdentifier identifier,
|
|
Callable<T> actionToDeleteObject) {
|
|
try {
|
|
SecurityUtils.getSubject().checkPermission(identifier.getStringPermission(DefaultActions.DELETE));
|
|
final T result = actionToDeleteObject.call();
|
|
deleteAllDataForRemovedObject(identifier);
|
|
return result;
|
|
} catch (UnauthorizedException e) {
|
|
throw e;
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void checkPermissionAndDeleteOwnershipForObjectRemoval(QualifiedObjectIdentifier identifier,
|
|
Action actionToDeleteObject) {
|
|
checkPermissionAndDeleteOwnershipForObjectRemoval(identifier, () -> {
|
|
actionToDeleteObject.run();
|
|
return null;
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void deleteAllDataForRemovedObject(QualifiedObjectIdentifier identifier) {
|
|
logger.info("Deleting ownerships for " + identifier);
|
|
deleteOwnership(identifier);
|
|
logger.info("Deleting acls for " + identifier);
|
|
deleteAccessControlList(identifier);
|
|
}
|
|
|
|
@Override
|
|
public <T extends WithQualifiedObjectIdentifier> void filterObjectsWithPermissionForCurrentUser(
|
|
HasPermissions.Action action, Iterable<T> objectsToFilter,
|
|
Consumer<T> filteredObjectsConsumer) {
|
|
objectsToFilter.forEach(objectToCheck -> {
|
|
if (SecurityUtils.getSubject().isPermitted(
|
|
objectToCheck.getIdentifier().getStringPermission(action))) {
|
|
filteredObjectsConsumer.accept(objectToCheck);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public <T extends WithQualifiedObjectIdentifier> void filterObjectsWithPermissionForCurrentUser(
|
|
HasPermissions.Action[] actions, Iterable<T> objectsToFilter,
|
|
Consumer<T> filteredObjectsConsumer) {
|
|
objectsToFilter.forEach(objectToCheck -> {
|
|
boolean isPermitted = actions.length > 0;
|
|
for (int i = 0; i < actions.length; i++) {
|
|
isPermitted &= SecurityUtils.getSubject().isPermitted(
|
|
objectToCheck.getIdentifier().getStringPermission(actions[i]));
|
|
}
|
|
if (isPermitted) {
|
|
filteredObjectsConsumer.accept(objectToCheck);
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Filters the objects with any of the given permissions for the current user */
|
|
@Override
|
|
public <T extends WithQualifiedObjectIdentifier> void filterObjectsWithAnyPermissionForCurrentUser(
|
|
HasPermissions.Action[] actions, Iterable<T> objectsToFilter,
|
|
Consumer<T> filteredObjectsConsumer) {
|
|
objectsToFilter.forEach(objectToCheck -> {
|
|
boolean isPermitted = false;
|
|
for (int i = 0; i < actions.length; i++) {
|
|
if (SecurityUtils.getSubject()
|
|
.isPermitted(objectToCheck.getIdentifier().getStringPermission(actions[i]))) {
|
|
isPermitted = true;
|
|
break;
|
|
}
|
|
}
|
|
if (isPermitted) {
|
|
filteredObjectsConsumer.accept(objectToCheck);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public <T extends WithQualifiedObjectIdentifier, R> List<R> mapAndFilterByReadPermissionForCurrentUser(
|
|
Iterable<T> objectsToFilter, Function<T, R> filteredObjectsMapper) {
|
|
final List<R> result = new ArrayList<>();
|
|
filterObjectsWithPermissionForCurrentUser(DefaultActions.READ, objectsToFilter,
|
|
filteredObject -> result.add(filteredObjectsMapper.apply(filteredObject)));
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public <T extends WithQualifiedObjectIdentifier, R> List<R> mapAndFilterByExplicitPermissionForCurrentUser(
|
|
HasPermissions.Action[] actions, Iterable<T> objectsToFilter,
|
|
Function<T, R> filteredObjectsMapper) {
|
|
final List<R> result = new ArrayList<>();
|
|
filterObjectsWithPermissionForCurrentUser(actions, objectsToFilter,
|
|
filteredObject -> result.add(filteredObjectsMapper.apply(filteredObject)));
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public <T extends WithQualifiedObjectIdentifier, R> List<R> mapAndFilterByAnyExplicitPermissionForCurrentUser(
|
|
HasPermissions permittedObject, HasPermissions.Action[] actions, Iterable<T> objectsToFilter,
|
|
Function<T, R> filteredObjectsMapper) {
|
|
final List<R> result = new ArrayList<>();
|
|
filterObjectsWithAnyPermissionForCurrentUser(actions, objectsToFilter,
|
|
filteredObject -> result.add(filteredObjectsMapper.apply(filteredObject)));
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public User getAllUser() {
|
|
return store.getUserByName(SecurityService.ALL_USERNAME);
|
|
}
|
|
|
|
@Override
|
|
public <T extends WithQualifiedObjectIdentifier> boolean hasCurrentUserRoleForOwnedObject(HasPermissions type, T object,
|
|
RoleDefinition roleToCheck) {
|
|
assert type != null;
|
|
assert object != null;
|
|
assert roleToCheck != null;
|
|
OwnershipAnnotation ownershipToCheck = getOwnership(object.getIdentifier());
|
|
return PermissionChecker.ownsUserASpecificRole(getCurrentUser(), getAllUser(),
|
|
ownershipToCheck == null ? null : ownershipToCheck.getAnnotation(), roleToCheck.getName());
|
|
}
|
|
|
|
@Override
|
|
public boolean hasCurrentUserMetaPermission(WildcardPermission permissionToCheck, Ownership ownership) {
|
|
if (hasPermissionsProvider == null) {
|
|
logger.warning(
|
|
"Missing HasPermissionsProvider for meta permission check. Using basic permission check that will produce false negatives in some cases.");
|
|
// In case we can not resolve all available HasPermissions instances, a meta permission check will not be
|
|
// able to produce the expected results.
|
|
// A basic permission check is done instead. This will potentially produce false negatives but never false
|
|
// positives.
|
|
return PermissionChecker.isPermitted(permissionToCheck, getCurrentUser(), getAllUser(), ownership, null);
|
|
} else {
|
|
return PermissionChecker.checkMetaPermission(permissionToCheck,
|
|
hasPermissionsProvider.getAllHasPermissions(), getCurrentUser(), getAllUser(), ownership);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean hasCurrentUserMetaPermissionWithOwnershipLookup(WildcardPermission permissionToCheck) {
|
|
if (hasPermissionsProvider == null) {
|
|
logger.warning(
|
|
"Missing HasPermissionsProvider for meta permission check. Using basic permission check that will produce false negatives in some cases.");
|
|
// In case we can not resolve all available HasPermissions instances, a meta permission check will not be
|
|
// able to produce the expected results.
|
|
// A basic permission check is done instead. This will potentially produce false negatives but never false
|
|
// positives.
|
|
return PermissionChecker.isPermitted(permissionToCheck, getCurrentUser(), getAllUser(), null, null);
|
|
} else {
|
|
return PermissionChecker.checkMetaPermissionWithOwnershipResolution(permissionToCheck,
|
|
hasPermissionsProvider.getAllHasPermissions(), getCurrentUser(), getAllUser(),
|
|
qualifiedObjectId -> {
|
|
OwnershipAnnotation ownershipAnnotation = accessControlStore.getOwnership(qualifiedObjectId);
|
|
return ownershipAnnotation == null ? null : ownershipAnnotation.getAnnotation();
|
|
});
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean hasCurrentUserAnyPermission(WildcardPermission permissionToCheck) {
|
|
if (hasPermissionsProvider == null) {
|
|
logger.warning(
|
|
"Missing HasPermissionsProvider for any permission check. Using basic permission check that will produce false negatives in some cases.");
|
|
// In case we can not resolve all available HasPermissions instances, an any permission check will not be
|
|
// able to produce the expected results.
|
|
// A basic permission check is done instead. This will potentially produce false negatives but never false
|
|
// positives.
|
|
return PermissionChecker.isPermitted(permissionToCheck, getCurrentUser(), getAllUser(), null, null);
|
|
} else {
|
|
return PermissionChecker.hasUserAnyPermission(permissionToCheck, hasPermissionsProvider.getAllHasPermissions(), getCurrentUser(), getAllUser(), null);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean hasCurrentUserMetaPermissionsOfRoleDefinitionWithQualification(final RoleDefinition roleDefinition, final Ownership qualificationForGrantedPermissions) {
|
|
boolean result = true;
|
|
for (WildcardPermission permissionToCheck : roleDefinition.getPermissions()) {
|
|
if (!hasCurrentUserMetaPermission(permissionToCheck, qualificationForGrantedPermissions)) {
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean hasCurrentUserMetaPermissionsOfRoleDefinitionsWithQualification(Set<RoleDefinition> roleDefinitions,
|
|
Ownership qualificationForGrantedPermissions) {
|
|
boolean result = true;
|
|
for (final RoleDefinition roleDefinition : roleDefinitions) {
|
|
result &= hasCurrentUserMetaPermissionsOfRoleDefinitionWithQualification(roleDefinition,
|
|
qualificationForGrantedPermissions);
|
|
if (!result) {
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public RoleDefinition getOrCreateRoleDefinitionFromPrototype(final RolePrototype rolePrototype) {
|
|
final RoleDefinition potentiallyExistingRoleDefinition = store.getRoleDefinition(rolePrototype.getId());
|
|
final RoleDefinition result;
|
|
if (potentiallyExistingRoleDefinition == null) {
|
|
result = store.createRoleDefinition(rolePrototype.getId(), rolePrototype.getName(),
|
|
rolePrototype.getPermissions());
|
|
setOwnership(result.getIdentifier(), null, getServerGroup());
|
|
} else {
|
|
result = potentiallyExistingRoleDefinition;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// ----------------- Replication -------------
|
|
@Override
|
|
public void clearReplicaState() throws MalformedURLException, IOException, InterruptedException {
|
|
store.clear();
|
|
accessControlStore.clear();
|
|
}
|
|
|
|
@Override
|
|
public Serializable getId() {
|
|
return getClass().getName();
|
|
}
|
|
|
|
@Override
|
|
public ObjectInputStream createObjectInputStreamResolvingAgainstCache(InputStream is) throws IOException {
|
|
return new ObjectInputStreamResolvingAgainstSecurityCache(is, store, null);
|
|
}
|
|
|
|
@Override
|
|
public void initiallyFillFromInternal(ObjectInputStream is) throws IOException, ClassNotFoundException,
|
|
InterruptedException {
|
|
logger.info("Reading cache manager...");
|
|
ReplicatingCacheManager newCacheManager = (ReplicatingCacheManager) is.readObject();
|
|
cacheManager.replaceContentsFrom(newCacheManager);
|
|
// overriding thread context class loader because the user store may be provided by a different bundle;
|
|
// We're assuming here that the user store service is provided by the same bundle in the replica as on the master.
|
|
ClassLoader oldCCL = Thread.currentThread().getContextClassLoader();
|
|
if (store != null) {
|
|
Thread.currentThread().setContextClassLoader(store.getClass().getClassLoader());
|
|
}
|
|
logger.info("Reading user store...");
|
|
try {
|
|
final UserStore newUserStore = (UserStore) is.readObject();
|
|
final UserGroup newServerGroup = newUserStore.getUserGroupByName(store.getServerGroupName());
|
|
// before replacing the local UserStore's contents, capture the server group:
|
|
final UserGroup oldServerGroup = store.getServerGroup();
|
|
store.replaceContentsFrom(newUserStore);
|
|
if (newServerGroup == null) {
|
|
// create the server group in a replication-aware fashion, making sure it appears on the master
|
|
final String serverGroupName = store.getServerGroupName();
|
|
final UUID serverGroupUuid = UUID.randomUUID();
|
|
createUserGroupWithInitialUser(serverGroupUuid, serverGroupName, /* initial user */ null);
|
|
store.setServerGroup(store.getUserGroupByName(store.getServerGroupName()));
|
|
} else if (newServerGroup != oldServerGroup) {
|
|
store.setServerGroup(newServerGroup);
|
|
}
|
|
} finally {
|
|
Thread.currentThread().setContextClassLoader(oldCCL);
|
|
}
|
|
if (accessControlStore != null) {
|
|
Thread.currentThread().setContextClassLoader(accessControlStore.getClass().getClassLoader());
|
|
}
|
|
logger.info("Reading access control store...");
|
|
try {
|
|
AccessControlStore newAccessControlStore = (AccessControlStore) is.readObject();
|
|
accessControlStore.replaceContentsFrom(newAccessControlStore);
|
|
} finally {
|
|
Thread.currentThread().setContextClassLoader(oldCCL);
|
|
}
|
|
// now ensure that the SERVER object has a valid ownership; use the (potentially new) server group as the default:
|
|
migrateServerObject();
|
|
logger.info("Reading isSharedAcrossSubdomains...");
|
|
sharedAcrossSubdomainsOf = (String) is.readObject();
|
|
logger.info("...as "+sharedAcrossSubdomainsOf);
|
|
logger.info("Reading baseUrlForCrossDomainStorage...");
|
|
baseUrlForCrossDomainStorage = (String) is.readObject();
|
|
logger.info("...as "+baseUrlForCrossDomainStorage);
|
|
logger.info("Done filling SecurityService");
|
|
}
|
|
|
|
@Override
|
|
public void serializeForInitialReplicationInternal(ObjectOutputStream objectOutputStream) throws IOException {
|
|
objectOutputStream.writeObject(cacheManager);
|
|
objectOutputStream.writeObject(store);
|
|
objectOutputStream.writeObject(accessControlStore);
|
|
objectOutputStream.writeObject(sharedAcrossSubdomainsOf);
|
|
objectOutputStream.writeObject(baseUrlForCrossDomainStorage);
|
|
}
|
|
|
|
@Override
|
|
public Iterable<OperationExecutionListener<ReplicableSecurityService>> getOperationExecutionListeners() {
|
|
return operationExecutionListeners.keySet();
|
|
}
|
|
|
|
@Override
|
|
public void addOperationExecutionListener(OperationExecutionListener<ReplicableSecurityService> listener) {
|
|
operationExecutionListeners.put(listener, listener);
|
|
}
|
|
|
|
@Override
|
|
public void removeOperationExecutionListener(OperationExecutionListener<ReplicableSecurityService> listener) {
|
|
operationExecutionListeners.remove(listener);
|
|
}
|
|
|
|
@Override
|
|
public ReplicationMasterDescriptor getMasterDescriptor() {
|
|
return replicatingFromMaster;
|
|
}
|
|
|
|
@Override
|
|
public void startedReplicatingFrom(ReplicationMasterDescriptor master) {
|
|
this.replicatingFromMaster = master;
|
|
}
|
|
|
|
@Override
|
|
public void stoppedReplicatingFrom(ReplicationMasterDescriptor master) {
|
|
this.replicatingFromMaster = null;
|
|
}
|
|
|
|
@Override
|
|
public void addOperationSentToMasterForReplication(
|
|
OperationWithResultWithIdWrapper<ReplicableSecurityService, ?> operationWithResultWithIdWrapper) {
|
|
this.operationsSentToMasterForReplication.add(operationWithResultWithIdWrapper);
|
|
}
|
|
|
|
@Override
|
|
public boolean hasSentOperationToMaster(OperationWithResult<ReplicableSecurityService, ?> operation) {
|
|
return this.operationsSentToMasterForReplication.remove(operation);
|
|
}
|
|
|
|
@Override
|
|
public void migrateOwnership(WithQualifiedObjectIdentifier identifier) {
|
|
migrateOwnership(identifier.getIdentifier(), identifier.getName());
|
|
}
|
|
|
|
@Override
|
|
public void migrateOwnership(final QualifiedObjectIdentifier identifier, final String displayName) {
|
|
this.migrateOwnership(identifier, null, /* setServerGroupAsOwner */ true, displayName);
|
|
}
|
|
|
|
@Override
|
|
public void migrateUser(final User user) {
|
|
// If no ownership migration was necessary, this is not a migration
|
|
if (migrateOwnership(user.getIdentifier(), user, /* setServerGroupAsOwner */ false, user.getName())) {
|
|
final String tenantNameForUsername = getDefaultTenantNameForUsername(user.getName());
|
|
// if there is already a default creation tenant set for the user, this is not a migration
|
|
// If the user's tenant already exists, this is no migration
|
|
if (user.getDefaultTenant(ServerInfo.getName()) == null
|
|
&& getUserGroupByName(tenantNameForUsername) == null) {
|
|
try {
|
|
final UserGroup tenantForUser = createTenantForUser(tenantNameForUsername, user);
|
|
setDefaultTenantForCurrentServerForUser(user.getName(), tenantForUser.getId());
|
|
} catch (UserGroupManagementException e) {
|
|
logger.log(Level.SEVERE, "Error during migration while creating tenant for user: " + user, e);
|
|
}
|
|
// Only adding user role if it is most probably a migration case
|
|
// In case an admin removes/changes the user role, it should not be recreated automatically
|
|
addUserRoleToUser(user);
|
|
final RoleDefinition adminRoleDefinition = getRoleDefinition(AdminRole.getInstance().getId());
|
|
for (Role roleOfUser : user.getRoles()) {
|
|
if (roleOfUser.getRoleDefinition().equals(adminRoleDefinition)) {
|
|
final UserGroup serverGroup = getServerGroup();
|
|
if (roleOfUser.getQualifiedForTenant() == null
|
|
|| roleOfUser.getQualifiedForTenant().equals(serverGroup)) {
|
|
// The user is a server admin -> Add it to the server group to allow setting the server
|
|
// group as default creation group
|
|
addUserToUserGroup(serverGroup, user);
|
|
if (UserStore.ADMIN_USERNAME.equals(user.getName())) {
|
|
// For the "admin" user the server group is initially set as
|
|
// default creation group. This is consistent to a newly created server
|
|
// and in most cases this is the group, the admin is meant to work with.
|
|
setDefaultTenantForCurrentServerForUser(user.getName(), getServerGroup().getId());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void migratePermission(final User user, final WildcardPermission permissionToMigrate, final Function<WildcardPermission, WildcardPermission> permissionReplacement) {
|
|
final WildcardPermission replacementPermissionOrNull = permissionReplacement.apply(permissionToMigrate);
|
|
final WildcardPermission effectivePermission;
|
|
if (replacementPermissionOrNull != null) {
|
|
// replacing legacy permission with a replacement
|
|
String username = user.getName();
|
|
removePermissionFromUser(username, permissionToMigrate);
|
|
addPermissionForUser(username, replacementPermissionOrNull);
|
|
effectivePermission = replacementPermissionOrNull;
|
|
} else {
|
|
effectivePermission = permissionToMigrate;
|
|
}
|
|
final TypeRelativeObjectIdentifier associationTypeIdentifier = PermissionAndRoleAssociation.get(effectivePermission, user);
|
|
final QualifiedObjectIdentifier associationQualifiedIdentifier = SecuredSecurityTypes.PERMISSION_ASSOCIATION
|
|
.getQualifiedObjectIdentifier(associationTypeIdentifier);
|
|
migrateOwnership(associationQualifiedIdentifier, associationQualifiedIdentifier.toString());
|
|
}
|
|
|
|
private boolean migrateOwnership(final QualifiedObjectIdentifier identifier, User userOwnerToSet,
|
|
boolean setServerGroupAsOwner, final String displayName) {
|
|
boolean wasNecessaryToMigrate = false;
|
|
final OwnershipAnnotation owner = this.getOwnership(identifier);
|
|
// initialize ownerships on migration and fix objects that were orphaned by e.g. deleting the owning user/group
|
|
if (owner == null
|
|
|| owner.getAnnotation().getTenantOwner() == null && owner.getAnnotation().getUserOwner() == null) {
|
|
final UserGroup tenantOwnerToSet = setServerGroupAsOwner ? this.getServerGroup() : null;
|
|
logger.info("missing Ownership fixed: Setting ownership for: " + identifier
|
|
+ " to tenant: "
|
|
+ tenantOwnerToSet + "; user: " + userOwnerToSet);
|
|
this.setOwnership(identifier, userOwnerToSet, tenantOwnerToSet, displayName);
|
|
wasNecessaryToMigrate = true;
|
|
}
|
|
migratedHasPermissionTypes.add(identifier.getTypeIdentifier());
|
|
return wasNecessaryToMigrate;
|
|
}
|
|
|
|
@Override
|
|
public void migrateServerObject() {
|
|
final QualifiedObjectIdentifier serverIdentifier = SecuredSecurityTypes.SERVER
|
|
.getQualifiedObjectIdentifier(
|
|
new TypeRelativeObjectIdentifier(ServerInfo.getName()));
|
|
migrateOwnership(serverIdentifier, serverIdentifier.toString());
|
|
}
|
|
|
|
@Override
|
|
public void checkMigration(Iterable<HasPermissions> allInstances) {
|
|
Class<? extends HasPermissions> clazz = Util.first(allInstances).getClass();
|
|
boolean allChecksSucessful = true;
|
|
for (HasPermissions shouldBeMigrated : allInstances) {
|
|
if (!migratedHasPermissionTypes.contains(shouldBeMigrated.getName())) {
|
|
logger.severe("ensure Ownership failed: Did not check Ownerships for " + clazz.getName()
|
|
+ " missing: " + shouldBeMigrated);
|
|
allChecksSucessful = false;
|
|
}
|
|
}
|
|
if (allChecksSucessful) {
|
|
logger.info("Ownership checks finished: Sucessfully checked all types in " + clazz.getName());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean hasCurrentUserReadPermission(WithQualifiedObjectIdentifier object) {
|
|
return object == null ? true :
|
|
SecurityUtils.getSubject().isPermitted(object.getPermissionType().getStringPermissionForObject(DefaultActions.READ, object));
|
|
}
|
|
|
|
@Override
|
|
public boolean hasCurrentUserUpdatePermission(WithQualifiedObjectIdentifier object) {
|
|
return object == null ? true :
|
|
SecurityUtils.getSubject().isPermitted(object.getPermissionType().getStringPermissionForObject(DefaultActions.UPDATE, object));
|
|
}
|
|
|
|
@Override
|
|
public boolean hasCurrentUserDeletePermission(WithQualifiedObjectIdentifier object) {
|
|
return object == null ? true :
|
|
SecurityUtils.getSubject().isPermitted(object.getPermissionType().getStringPermissionForObject(DefaultActions.DELETE, object));
|
|
}
|
|
|
|
public boolean hasCurrentUserExplicitPermissions(WithQualifiedObjectIdentifier object, HasPermissions.Action... actions) {
|
|
boolean isPermitted = true;
|
|
if (object != null) {
|
|
for (int i = 0; i < actions.length; i++) {
|
|
isPermitted &= SecurityUtils.getSubject().isPermitted(object.getPermissionType().getStringPermissionForObject(actions[i], object));
|
|
}
|
|
}
|
|
return isPermitted;
|
|
}
|
|
|
|
public boolean hasCurrentUserOneOfExplicitPermissions(WithQualifiedObjectIdentifier object, HasPermissions.Action... actions) {
|
|
boolean result = object == null;
|
|
if (object != null) {
|
|
for (com.sap.sse.security.shared.HasPermissions.Action action : actions) {
|
|
if (hasCurrentUserExplicitPermissions(object, action)) {
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public void checkCurrentUserReadPermission(WithQualifiedObjectIdentifier object) {
|
|
if (object != null) {
|
|
SecurityUtils.getSubject().checkPermission(object.getPermissionType().getStringPermissionForObject(DefaultActions.READ, object));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void checkCurrentUserUpdatePermission(WithQualifiedObjectIdentifier object) {
|
|
if (object != null) {
|
|
SecurityUtils.getSubject().checkPermission(object.getPermissionType().getStringPermissionForObject(DefaultActions.UPDATE, object));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void checkCurrentUserDeletePermission(WithQualifiedObjectIdentifier object) {
|
|
if (object != null) {
|
|
SecurityUtils.getSubject().checkPermission(object.getPermissionType().getStringPermissionForObject(DefaultActions.DELETE, object));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void checkCurrentUserDeletePermission(QualifiedObjectIdentifier identifier) {
|
|
if (identifier != null) {
|
|
SecurityUtils.getSubject().checkPermission(identifier.getStringPermission(DefaultActions.DELETE));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void checkCurrentUserExplicitPermissions(WithQualifiedObjectIdentifier object, HasPermissions.Action... actions) {
|
|
if (object != null) {
|
|
for (int i = 0; i < actions.length; i++) {
|
|
SecurityUtils.getSubject().checkPermission(object.getPermissionType().getStringPermissionForObject(actions[i], object));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void checkCurrentUserHasOneOfExplicitPermissions(WithQualifiedObjectIdentifier object, HasPermissions.Action... actions) {
|
|
if (object != null) {
|
|
boolean isPermitted = false;
|
|
for (int i = 0; i < actions.length; i++) {
|
|
if (SecurityUtils.getSubject()
|
|
.isPermitted(object.getPermissionType().getStringPermissionForObject(actions[i], object))) {
|
|
isPermitted = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!isPermitted) {
|
|
throw new AuthorizationException();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void assumeOwnershipMigrated(String typeName) {
|
|
migratedHasPermissionTypes.add(typeName);
|
|
}
|
|
|
|
@Override
|
|
public boolean hasUserAllWildcardPermissionsForAlreadyRealizedQualifications(RoleDefinition role,
|
|
Iterable<WildcardPermission> permissionsToCheck) {
|
|
Pair<Boolean, Set<Ownership>> qualificationsToCheck = store.getExistingQualificationsForRoleDefinition(role);
|
|
final Iterable<Ownership> effectiveQualificationsToCheck = Boolean.TRUE.equals(qualificationsToCheck.getA())
|
|
? Collections.singletonList(null)
|
|
: qualificationsToCheck.getB();
|
|
for (WildcardPermission permission : permissionsToCheck) {
|
|
for (Ownership ownership : effectiveQualificationsToCheck) {
|
|
if (!hasCurrentUserMetaPermission(permission, ownership)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public <T> T getPreferenceObject(String username, String key) {
|
|
return store.getPreferenceObject(username, key);
|
|
}
|
|
|
|
@Override
|
|
public void setDefaultTenantForCurrentServerForUser(String username, UUID defaultTenantId) {
|
|
final String serverName = ServerInfo.getName();
|
|
apply(s->s.internalSetDefaultTenantForServerForUser(username, defaultTenantId, serverName));
|
|
}
|
|
|
|
@Override
|
|
public Void internalSetDefaultTenantForServerForUser(String username, UUID defaultTenantId, String serverName) {
|
|
User user = getUserByName(username);
|
|
UserGroup newDefaultTenant = getUserGroup(defaultTenantId);
|
|
user.setDefaultTenant(newDefaultTenant, serverName);
|
|
store.updateUser(user);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
/**
|
|
* This method does not handle RoleAssociationOwnerships! this must be done via the callback
|
|
*/
|
|
public void copyUsersAndRoleAssociations(UserGroup source, UserGroup destination, RoleCopyListener callback) {
|
|
for (User user : source.getUsers()) {
|
|
addUserToUserGroup(destination, user);
|
|
}
|
|
|
|
for (Map.Entry<RoleDefinition, Boolean> entr : source.getRoleDefinitionMap().entrySet()) {
|
|
putRoleDefinitionToUserGroup(destination, entr.getKey(), entr.getValue());
|
|
}
|
|
|
|
for (Pair<User, Role> userAndRole : store.getRolesQualifiedByUserGroup(source)) {
|
|
final Role existingRole = userAndRole.getB();
|
|
final Role copyRole = new Role(existingRole.getRoleDefinition(), destination,
|
|
existingRole.getQualifiedForUser());
|
|
addRoleForUser(userAndRole.getA(),
|
|
copyRole);
|
|
callback.onRoleCopy(userAndRole.getA(), existingRole, copyRole);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public <T> T doWithTemporaryDefaultTenant(UserGroup tenant, Callable<T> action) {
|
|
final UserGroup previousValue = temporaryDefaultTenant.get();
|
|
temporaryDefaultTenant.set(tenant);
|
|
try {
|
|
return action.call();
|
|
} catch (RuntimeException e) {
|
|
throw e;
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
} finally {
|
|
temporaryDefaultTenant.set(previousValue);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
// See com.sap.sse.security.impl.Activator.clearState(), moved due to required reinitialisation sequence for
|
|
// permission-vertical
|
|
public void clearState() throws Exception {
|
|
}
|
|
|
|
@Override
|
|
public void setUnsentOperationToMasterSender(OperationsToMasterSendingQueue service) {
|
|
this.unsentOperationsToMasterSender = service;
|
|
}
|
|
@Override
|
|
public void storeSession(String cacheName, Session session) {
|
|
PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory().storeSession(cacheName, session);
|
|
}
|
|
|
|
@Override
|
|
public <S, O extends OperationWithResult<S, ?>, T> void scheduleForSending(
|
|
OperationWithResult<S, T> operationWithResult, OperationsToMasterSender<S, O> sender) {
|
|
if (unsentOperationsToMasterSender != null) {
|
|
unsentOperationsToMasterSender.scheduleForSending(operationWithResult, sender);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void removeSession(String cacheName, Session session) {
|
|
PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory().removeSession(cacheName, session);
|
|
}
|
|
|
|
@Override
|
|
public void removeAllSessions(String cacheName) {
|
|
PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory().removeAllSessions(cacheName);
|
|
}
|
|
|
|
@Override
|
|
public boolean isInitialOrMigration() {
|
|
return isInitialOrMigration;
|
|
}
|
|
|
|
@Override
|
|
public String getSharedAcrossSubdomainsOf() {
|
|
return sharedAcrossSubdomainsOf;
|
|
}
|
|
|
|
@Override
|
|
public String getBaseUrlForCrossDomainStorage() {
|
|
return baseUrlForCrossDomainStorage;
|
|
}
|
|
}
|