blob: d673427b3c7c4e42a98b8aa98407d7fad54519e8 [file] [log] [blame]
package de.ids_mannheim.korap.authentication;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// import com.novell.ldap.*; search() funktioniert nicht korrekt, ausgewechselt gegen unboundID's Bibliothek 20.04.17/FB
//Using JAR from unboundID:
import com.unboundid.ldap.sdk.LDAPException;
import de.ids_mannheim.korap.auditing.AuditRecord;
import de.ids_mannheim.korap.config.Attributes;
import de.ids_mannheim.korap.config.AuthenticationMethod;
import de.ids_mannheim.korap.config.BeansFactory;
import de.ids_mannheim.korap.config.FullConfiguration;
import de.ids_mannheim.korap.config.TokenType;
import de.ids_mannheim.korap.config.URIParam;
import de.ids_mannheim.korap.exceptions.EmptyResultException;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.NotAuthorizedException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.exceptions.WrappedException;
import de.ids_mannheim.korap.interfaces.AuthenticationIface;
import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
import de.ids_mannheim.korap.interfaces.EncryptionIface;
import de.ids_mannheim.korap.interfaces.ValidatorIface;
import de.ids_mannheim.korap.interfaces.db.AdminHandlerIface;
import de.ids_mannheim.korap.interfaces.db.AuditingIface;
import de.ids_mannheim.korap.interfaces.db.EntityHandlerIface;
import de.ids_mannheim.korap.interfaces.db.UserDataDbIface;
import de.ids_mannheim.korap.interfaces.defaults.ApacheValidator;
import de.ids_mannheim.korap.user.DemoUser;
import de.ids_mannheim.korap.user.KorAPUser;
import de.ids_mannheim.korap.user.ShibUser;
import de.ids_mannheim.korap.user.TokenContext;
import de.ids_mannheim.korap.user.User;
import de.ids_mannheim.korap.user.User.CorpusAccess;
import de.ids_mannheim.korap.user.User.Location;
import de.ids_mannheim.korap.user.UserDetails;
import de.ids_mannheim.korap.user.UserSettings;
import de.ids_mannheim.korap.user.Userdata;
import de.ids_mannheim.korap.utils.TimeUtils;
/**
* contains the logic to authentication and registration processes. Uses
* interface implementations (AuthenticationIface) for different databases and
* handlers
*
* @author hanl
*/
public class KustvaktAuthenticationManager extends AuthenticationManagerIface {
private static Logger jlog = LoggerFactory.getLogger(KustvaktAuthenticationManager.class);
private EncryptionIface crypto;
private EntityHandlerIface entHandler;
private AdminHandlerIface adminHandler;
private AuditingIface auditing;
private FullConfiguration config;
private Collection userdatadaos;
private LoginCounter counter;
private ValidatorIface validator;
public KustvaktAuthenticationManager(EntityHandlerIface userdb, AdminHandlerIface admindb, EncryptionIface crypto,
FullConfiguration config, AuditingIface auditer, Collection<UserDataDbIface> userdatadaos) {
this.entHandler = userdb;
this.adminHandler = admindb;
this.config = config;
this.crypto = crypto;
this.auditing = auditer;
this.counter = new LoginCounter(config);
this.userdatadaos = userdatadaos;
// todo: load via beancontext
try {
this.validator = new ApacheValidator();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* get session object if token was a session token
*
* @param token
* @param host
* @param useragent
* @return
* @throws KustvaktException
*/
@Override
public TokenContext getTokenContext(TokenType type, String token,
String host, String useragent) throws KustvaktException {
AuthenticationIface provider = getProvider(type , null);
if (provider == null)
// throw exception for missing type parameter
throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
"token type not defined or found", "token_type");
TokenContext context = provider.getTokenContext(token);
// if (!matchStatus(host, useragent, context))
// provider.removeUserSession(token);
return context;
}
@Override
public User getUser(String username) throws KustvaktException {
// User user;
// Object value = this.getCacheValue(username);
if (User.UserFactory.isDemo(username))
return User.UserFactory.getDemoUser();
// if (value != null) {
// Map map = (Map) value;
// user = User.UserFactory.toUser(map);
// }
// else {
// user = entHandler.getAccount(username);
// this.storeInCache(username, user.toCache());
// todo: not valid. for the duration of the session, the host should not
// change!
// }
// todo:
// user.addField(Attributes.HOST, context.getHostAddress());
// user.addField(Attributes.USER_AGENT, context.getUserAgent());
//EM:copied from EntityDao
KorAPUser user = new KorAPUser(); // oder eigentlich new DemoUser oder new DefaultUser.
user.setUsername(username);
return user;
// return entHandler.getAccount(username);
}
public TokenContext refresh(TokenContext context) throws KustvaktException {
AuthenticationIface provider = getProvider(context.getTokenType(), null);
if (provider == null) {
// todo:
}
try {
provider.removeUserSession(context.getToken());
User user = getUser(context.getUsername());
return provider.createTokenContext(user, context.params());
} catch (KustvaktException e) {
throw new WrappedException(e, StatusCodes.LOGIN_FAILED);
}
}
/** EM: fix type is not flexible
* @param type
* @param attributes
* contains username and password to authenticate the user.
* Depending of the authentication schema, may contain other
* values as well
* @return User
* @throws KustvaktException
*/
@Override
public User authenticate(AuthenticationMethod method, String username, String password, Map<String, Object> attributes)
throws KustvaktException {
User user;
switch (method) {
case SHIBBOLETH:
// todo:
user = authenticateShib(attributes);
break;
case LDAP:
// IdM/LDAP: (09.02.17/FB)
user = authenticateIdM(username, password, attributes);
break;
default:
user = authenticate(username, password, attributes);
break;
}
auditing.audit(AuditRecord.serviceRecord(user.getId(), StatusCodes.LOGIN_SUCCESSFUL, user.toString()));
return user;
}
// a. set location depending on X-Forwarded-For.
// X-Forwarded-For: clientIP, ProxyID, ProxyID...
// the following private address spaces may be used to define intranet
// spaces:
// 10.0.0.0 - 10.255.255.255 (10/8 prefix)
// 172.16.0.0 - 172.31.255.255 (172.16/12 prefix)
// 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)
// b. set corpusAccess depending on location:
// c. DemoUser only gets corpusAccess=FREE.
// 16.05.17/FB
@Override
public void setAccessAndLocation(User user, HttpHeaders headers) {
Boolean DEBUG_LOG = true;
MultivaluedMap<String, String> headerMap = headers.getRequestHeaders();
Location location = Location.EXTERN;
CorpusAccess corpusAccess = CorpusAccess.FREE;
if( user instanceof DemoUser )
{
// to be absolutely sure:
user.setCorpusAccess(User.CorpusAccess.FREE);
if( DEBUG_LOG == true )
System.out.printf("setAccessAndLocation: DemoUser: location=%s, access=%s.\n", user.locationtoString(), user.accesstoString());
return;
}
if (headerMap != null && headerMap.containsKey(org.eclipse.jetty.http.HttpHeaders.X_FORWARDED_FOR)) {
String[] vals = headerMap.getFirst(org.eclipse.jetty.http.HttpHeaders.X_FORWARDED_FOR).split(",");
String clientAddress = vals[0];
try {
InetAddress ip = InetAddress.getByName(clientAddress);
if (ip.isSiteLocalAddress()){
location = Location.INTERN;
corpusAccess = CorpusAccess.ALL;
}
else{
corpusAccess = CorpusAccess.PUB;
}
jlog.debug(String.format("X-Forwarded-For : '%s' (%d values) -> %s\n",
Arrays.toString(vals), vals.length, vals[0]));
jlog.debug(String.format("X-Forwarded-For : location = %s corpusAccess = %s\n",
location == Location.INTERN ? "INTERN" : "EXTERN", corpusAccess == CorpusAccess.ALL ? "ALL"
: corpusAccess == CorpusAccess.PUB ? "PUB" : "FREE"));
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
user.setLocation(location);
user.setCorpusAccess(corpusAccess);
if( DEBUG_LOG == true )
System.out.printf("setAccessAndLocation: KorAPUser: location=%s, access=%s.\n", user.locationtoString(), user.accesstoString());
}
} // getAccess
@Override
public TokenContext createTokenContext(User user, Map<String, Object> attr, TokenType type)
throws KustvaktException {
// use api token
AuthenticationIface provider = getProvider(type, TokenType.API);
// EM: not in the new DB
// if (attr.get(Attributes.SCOPES) != null)
// this.getUserData(user, UserDetails.class);
TokenContext context = provider.createTokenContext(user, attr);
if (context == null)
throw new KustvaktException(StatusCodes.NOT_SUPPORTED);
context.setUserAgent((String) attr.get(Attributes.USER_AGENT));
context.setHostAddress(Attributes.HOST);
return context;
}
// todo: test
@Deprecated
private boolean matchStatus(String host, String useragent, TokenContext context) {
if (host.equals(context.getHostAddress())) {
if (useragent.equals(context.getUserAgent()))
return true;
}
return false;
}
private User authenticateShib(Map<String, Object> attributes) throws KustvaktException {
// todo use persistent id, since eppn is not unique
String eppn = (String) attributes.get(Attributes.EPPN);
if (eppn == null || eppn.isEmpty())
throw new KustvaktException(StatusCodes.REQUEST_INVALID);
if (!attributes.containsKey(Attributes.EMAIL) && validator.isValid(eppn, Attributes.EMAIL))
attributes.put(Attributes.EMAIL, eppn);
User user = null;
if (isRegistered(eppn))
user = createShibbUserAccount(attributes);
return user;
}
// todo: what if attributes null?
private User authenticate(String username, String password, Map<String, Object> attr) throws KustvaktException {
Map<String, Object> attributes = validator.validateMap(attr);
User unknown;
// just to make sure that the plain password does not appear anywhere in
// the logs!
try {
validator.validateEntry(username, Attributes.USERNAME);
} catch (KustvaktException e) {
throw new WrappedException(e, StatusCodes.LOGIN_FAILED, username);
}
if (username == null || username.isEmpty())
throw new WrappedException(new KustvaktException(username, StatusCodes.BAD_CREDENTIALS),
StatusCodes.LOGIN_FAILED);
else {
try {
unknown = entHandler.getAccount(username);
} catch (EmptyResultException e) {
// mask exception to disable user guessing in possible attacks
throw new WrappedException(new KustvaktException(username, StatusCodes.BAD_CREDENTIALS),
StatusCodes.LOGIN_FAILED, username);
} catch (KustvaktException e) {
jlog.error("Error: {}", e);
throw new WrappedException(e, StatusCodes.LOGIN_FAILED, attributes.toString());
}
}
boolean isAdmin = adminHandler.isAdmin(unknown.getId());
unknown.setAdmin(isAdmin);
jlog.debug("Authentication: found username " + unknown.getUsername());
if (unknown instanceof KorAPUser) {
if (password == null || password.isEmpty())
throw new WrappedException(new KustvaktException(unknown.getId(), StatusCodes.BAD_CREDENTIALS),
StatusCodes.LOGIN_FAILED, username);
KorAPUser user = (KorAPUser) unknown;
boolean check = crypto.checkHash(password, user.getPassword());
if (!check) {
// the fail counter only applies for wrong password
jlog.warn("Wrong Password!");
processLoginFail(unknown);
throw new WrappedException(new KustvaktException(user.getId(), StatusCodes.BAD_CREDENTIALS),
StatusCodes.LOGIN_FAILED, username);
}
// bad credentials error has precedence over account locked or
// unconfirmed codes
// since latter can lead to account guessing of third parties
if (user.isAccountLocked()) {
URIParam param = (URIParam) user.getField(URIParam.class);
if (param.hasValues()) {
jlog.debug("Account is not yet activated for user '{}'", user.getUsername());
if (TimeUtils.getNow().isAfter(param.getUriExpiration())) {
jlog.error("URI token is expired. Deleting account for user {}", user.getUsername());
deleteAccount(user);
throw new WrappedException(
new KustvaktException(unknown.getId(), StatusCodes.EXPIRED,
"account confirmation uri has expired!", param.getUriFragment()),
StatusCodes.LOGIN_FAILED, username);
}
throw new WrappedException(
new KustvaktException(unknown.getId(), StatusCodes.ACCOUNT_NOT_CONFIRMED),
StatusCodes.LOGIN_FAILED, username);
}
jlog.error("ACCESS DENIED: account not active for '{}'", unknown.getUsername());
throw new WrappedException(new KustvaktException(unknown.getId(), StatusCodes.ACCOUNT_DEACTIVATED),
StatusCodes.LOGIN_FAILED, username);
}
} else if (unknown instanceof ShibUser) {
// todo
}
jlog.debug("Authentication done: "+unknown);
return unknown;
}
/**
* authenticate using IdM (Identitätsmanagement) accessed by LDAP.
*
* @param username
* @param password
* @param attr
* @return
* @throws KustvaktException
* @date 09.02.17/FB
*/
// todo: what if attributes null?
private User authenticateIdM(String username, String password, Map<String, Object> attr) throws KustvaktException {
Map<String, Object> attributes = validator.validateMap(attr);
User unknown = null;
// just to make sure that the plain password does not appear anywhere in
// the logs!
System.out.printf("Debug: authenticateIdM: entering for '%s'...\n", username);
/**
* wozu Apache Validatoren für User/Passwort für IdM/LDAP? siehe
* validation.properties. Abgeschaltet 21.04.17/FB try {
* validator.validateEntry(username, Attributes.USERNAME); } catch
* (KustvaktException e) { throw new WrappedException(e,
* StatusCodes.LOGIN_FAILED, username); }
*/
if (username == null || username.isEmpty() || password == null || password.isEmpty())
throw new WrappedException(new KustvaktException(username, StatusCodes.BAD_CREDENTIALS),
StatusCodes.LOGIN_FAILED);
// LDAP Access:
try {
// todo: unknown = ...
int ret = LdapAuth3.login(username, password, config.getLdapConfig());
System.out.printf("Debug: autenticationIdM: Ldap.login(%s) returns: %d.\n", username, ret);
if (ret != LdapAuth3.LDAP_AUTH_ROK) {
jlog.error("LdapAuth3.login(username='{}') returns '{}'='{}'!", username, ret,
LdapAuth3.getErrMessage(ret));
// mask exception to disable user guessing in possible attacks
/*
* by Hanl throw new WrappedException(new
* KustvaktException(username, StatusCodes.BAD_CREDENTIALS),
* StatusCodes.LOGIN_FAILED, username);
*/
throw new WrappedException(new KustvaktException(username, StatusCodes.LDAP_BASE_ERRCODE + ret,
LdapAuth3.getErrMessage(ret), null), StatusCodes.LOGIN_FAILED, username);
}
} catch (LDAPException e) {
jlog.error("Error: username='{}' -> '{}'!", username, e);
// mask exception to disable user guessing in possible attacks
/*
* by Hanl: throw new WrappedException(new
* KustvaktException(username, StatusCodes.BAD_CREDENTIALS),
* StatusCodes.LOGIN_FAILED, username);
*/
throw new WrappedException(
new KustvaktException(username, StatusCodes.LDAP_BASE_ERRCODE + LdapAuth3.LDAP_AUTH_RINTERR,
LdapAuth3.getErrMessage(LdapAuth3.LDAP_AUTH_RINTERR), null),
StatusCodes.LOGIN_FAILED, username);
}
// Create a User
// TODO: KorAPUser für solche mit einem bestehenden Account
// DefaultUser sonst.
User user = new KorAPUser();
user.setUsername(username);
/*
* folgender Code funktioniert hier noch nicht, da die Headers noch
* nicht ausgewertet worden sind - 23.05.17/FB Object o =
* attr.get(Attributes.LOCATION); String loc = (String)o.toString(); int
* location = Integer.parseInt(loc); user.setLocation(location);
* user.setCorpusAccess(Integer.parseInt(attr.get(Attributes.
* CORPUS_ACCESS).toString()));
*/
unknown = user;
jlog.trace("Authentication: found username " + unknown.getUsername());
if (unknown instanceof KorAPUser) {
/*
* password already checked using LDAP: if (password == null ||
* password.isEmpty()) throw new WrappedException(new
* KustvaktException( unknown.getId(), StatusCodes.BAD_CREDENTIALS),
* StatusCodes.LOGIN_FAILED, username);
*
* KorAPUser user = (KorAPUser) unknown; boolean check =
* crypto.checkHash(password, user.getPassword());
*
* if (!check) { // the fail counter only applies for wrong password
* jlog.warn("Wrong Password!"); processLoginFail(unknown); throw
* new WrappedException(new KustvaktException(user.getId(),
* StatusCodes.BAD_CREDENTIALS), StatusCodes.LOGIN_FAILED,
* username); }
*/
// bad credentials error has precedence over account locked or
// unconfirmed codes
// since latter can lead to account guessing of third parties
/*
* if (user.isAccountLocked()) {
*
* URIParam param = (URIParam) user.getField(URIParam.class);
*
* if (param.hasValues()) {
* jlog.debug("Account is not yet activated for user '{}'",
* user.getUsername()); if
* (TimeUtils.getNow().isAfter(param.getUriExpiration())) {
* jlog.error( "URI token is expired. Deleting account for user {}",
* user.getUsername()); deleteAccount(user); throw new
* WrappedException(new KustvaktException( unknown.getId(),
* StatusCodes.EXPIRED, "account confirmation uri has expired!",
* param.getUriFragment()), StatusCodes.LOGIN_FAILED, username); }
* throw new WrappedException(new KustvaktException(
* unknown.getId(), StatusCodes.ACCOUNT_NOT_CONFIRMED),
* StatusCodes.LOGIN_FAILED, username); }
* jlog.error("ACCESS DENIED: account not active for '{}'",
* unknown.getUsername()); throw new WrappedException(new
* KustvaktException( unknown.getId(),
* StatusCodes.ACCOUNT_DEACTIVATED), StatusCodes.LOGIN_FAILED,
* username); }
*/
} else if (unknown instanceof ShibUser) {
// todo
}
jlog.debug("Authentication done: " + username);
return unknown;
} // authenticateIdM
public boolean isRegistered(String username) {
User user;
if (username == null || username.isEmpty())
return false;
// throw new KustvaktException(username, StatusCodes.ILLEGAL_ARGUMENT,
// "username must be set", username);
try {
user = entHandler.getAccount(username);
} catch (EmptyResultException e) {
jlog.debug("user does not exist ({})", username);
return false;
} catch (KustvaktException e) {
jlog.error("KorAPException", e.string());
return false;
// throw new KustvaktException(username,
// StatusCodes.ILLEGAL_ARGUMENT,
// "username invalid", username);
}
return user != null;
}
public void logout(TokenContext context) throws KustvaktException {
try {
AuthenticationIface provider = getProvider(context.getTokenType(), null);
if (provider == null) {
throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT, "Authentication "
+ "provider not supported!", context.getTokenType().displayName());
}
provider.removeUserSession(context.getToken());
} catch (KustvaktException e) {
throw new WrappedException(e, StatusCodes.LOGOUT_FAILED, context.toString());
}
auditing.audit(
AuditRecord.serviceRecord(context.getUsername(), StatusCodes.LOGOUT_SUCCESSFUL, context.toString()));
this.removeCacheEntry(context.getToken());
}
private void processLoginFail(User user) throws KustvaktException {
counter.registerFail(user.getUsername());
if (!counter.validate(user.getUsername())) {
try {
this.lockAccount(user);
} catch (KustvaktException e) {
jlog.error("user account could not be locked", e);
throw new WrappedException(e, StatusCodes.UPDATE_ACCOUNT_FAILED);
}
throw new WrappedException(new KustvaktException(user.getId(), StatusCodes.ACCOUNT_DEACTIVATED),
StatusCodes.LOGIN_FAILED);
}
}
public void lockAccount(User user) throws KustvaktException {
if (!(user instanceof KorAPUser))
throw new KustvaktException(StatusCodes.REQUEST_INVALID);
KorAPUser u = (KorAPUser) user;
u.setAccountLocked(true);
jlog.info("locking account for user: {}", user.getUsername());
entHandler.updateAccount(u);
}
public KorAPUser checkPasswordAllowance(KorAPUser user, String oldPassword, String newPassword)
throws KustvaktException {
String dbPassword = user.getPassword();
if (oldPassword.trim().equals(newPassword.trim())) {
// TODO: special error StatusCodes for this?
throw new WrappedException(new KustvaktException(user.getId(), StatusCodes.ILLEGAL_ARGUMENT),
StatusCodes.PASSWORD_RESET_FAILED, newPassword);
}
boolean check = crypto.checkHash(oldPassword, dbPassword);
if (!check)
throw new WrappedException(new KustvaktException(user.getId(), StatusCodes.BAD_CREDENTIALS),
StatusCodes.PASSWORD_RESET_FAILED);
try {
user.setPassword(crypto.secureHash(newPassword));
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
throw new WrappedException(
new KustvaktException(user.getId(), StatusCodes.ILLEGAL_ARGUMENT, "password invalid", newPassword),
StatusCodes.PASSWORD_RESET_FAILED, user.toString(), newPassword);
}
return user;
}
// fixme: use clientinfo for logging/auditing?! = from where did he access
// the reset function?
@Override
public void resetPassword(String uriFragment, String username, String newPassphrase) throws KustvaktException {
try {
validator.validateEntry(username, Attributes.USERNAME);
validator.validateEntry(newPassphrase, Attributes.PASSWORD);
} catch (KustvaktException e) {
jlog.error("Error: {}", e.string());
throw new WrappedException(
new KustvaktException(username, StatusCodes.ILLEGAL_ARGUMENT, "password invalid", newPassphrase),
StatusCodes.PASSWORD_RESET_FAILED, username, newPassphrase);
}
try {
newPassphrase = crypto.secureHash(newPassphrase);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
jlog.error("Encoding/Algorithm Error", e);
throw new WrappedException(
new KustvaktException(username, StatusCodes.ILLEGAL_ARGUMENT, "password invalid", newPassphrase),
StatusCodes.PASSWORD_RESET_FAILED, username, uriFragment, newPassphrase);
}
int result = entHandler.resetPassphrase(username, uriFragment, newPassphrase);
if (result == 0)
throw new WrappedException(
new KustvaktException(username, StatusCodes.EXPIRED, "URI fragment expired", uriFragment),
StatusCodes.PASSWORD_RESET_FAILED, username, uriFragment);
else if (result == 1)
jlog.info("successfully reset password for user {}", username);
}
public void confirmRegistration(String uriFragment, String username) throws KustvaktException {
try {
validator.validateEntry(username, Attributes.USERNAME);
} catch (KustvaktException e) {
jlog.error("Error: {}", e.string());
throw new WrappedException(e, StatusCodes.ACCOUNT_CONFIRMATION_FAILED, username, uriFragment);
}
int r = entHandler.activateAccount(username, uriFragment);
if (r == 0) {
User user;
try {
user = entHandler.getAccount(username);
} catch (EmptyResultException e) {
throw new WrappedException(new KustvaktException(username, StatusCodes.BAD_CREDENTIALS),
StatusCodes.ACCOUNT_CONFIRMATION_FAILED, username, uriFragment);
}
entHandler.deleteAccount(user.getId());
throw new WrappedException(new KustvaktException(user.getId(), StatusCodes.EXPIRED),
StatusCodes.ACCOUNT_CONFIRMATION_FAILED, username, uriFragment);
} else if (r == 1)
jlog.info("successfully confirmed user registration for user {}", username);
// register successful audit!
}
/**
* @param attributes
* @return
* @throws KustvaktException
*/
// fixme: remove clientinfo object (not needed), use json representation to
// get stuff
public User createUserAccount(Map<String, Object> attributes, boolean confirmation_required)
throws KustvaktException {
Map<String, Object> safeMap = validator.validateMap(attributes);
if (safeMap.get(Attributes.USERNAME) == null || ((String) safeMap.get(Attributes.USERNAME)).isEmpty())
throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT, "username must be set", "username");
if (safeMap.get(Attributes.PASSWORD) == null || ((String) safeMap.get(Attributes.PASSWORD)).isEmpty())
throw new KustvaktException(safeMap.get(Attributes.USERNAME), StatusCodes.ILLEGAL_ARGUMENT,
"password must be set", "password");
String username = validator.validateEntry((String) safeMap.get(Attributes.USERNAME), Attributes.USERNAME);
String password = validator.validateEntry((String) safeMap.get(Attributes.PASSWORD), Attributes.PASSWORD);
String hash;
try {
hash = crypto.secureHash(password);
} catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
jlog.error("Encryption error", e);
throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
}
KorAPUser user = User.UserFactory.getUser(username);
Object id = attributes.get(Attributes.ID);
if (id != null && id instanceof Integer)
user.setId((Integer) id);
user.setAccountLocked(confirmation_required);
if (confirmation_required) {
URIParam param = new URIParam(crypto.createToken(),
TimeUtils.plusSeconds(config.getTokenTTL()).getMillis());
user.addField(param);
}
user.setPassword(hash);
String o = (String) attributes.get(Attributes.IS_ADMIN);
boolean b = Boolean.parseBoolean(o);
user.setAdmin(b);
try {
UserDetails details = new UserDetails();
details.read(safeMap, true);
UserSettings settings = new UserSettings();
settings.read(safeMap, true);
jlog.info("Creating new user account for user {}", user.getUsername());
entHandler.createAccount(user);
if (user.isAdmin() && user instanceof KorAPUser) {
adminHandler.addAccount(user);
user.setCorpusAccess(CorpusAccess.ALL);
}
details.setUserId(user.getId());
settings.setUserId(user.getId());
UserDataDbIface dao = BeansFactory.getTypeFactory().getTypeInterfaceBean(userdatadaos, UserDetails.class);
// todo: remove this
assert dao != null;
dao.store(details);
dao = BeansFactory.getTypeFactory().getTypeInterfaceBean(userdatadaos, UserSettings.class);
assert dao != null;
dao.store(settings);
} catch (KustvaktException e) {
jlog.error("Error: {}", e.string());
throw new WrappedException(e, StatusCodes.CREATE_ACCOUNT_FAILED, user.toString());
}
auditing.audit(AuditRecord.serviceRecord(user.getUsername(), StatusCodes.CREATE_ACCOUNT_SUCCESSFUL));
return user;
}
// todo:
private ShibUser createShibbUserAccount(Map<String, Object> attributes) throws KustvaktException {
jlog.debug("creating shibboleth user account for user attr: {}", attributes);
Map<String, Object> safeMap = validator.validateMap(attributes);
// todo eppn non-unique.join with idp or use persistent_id as username
// identifier
ShibUser user = User.UserFactory.getShibInstance((String) safeMap.get(Attributes.EPPN),
(String) safeMap.get(Attributes.MAIL), (String) safeMap.get(Attributes.CN));
user.setAffiliation((String) safeMap.get(Attributes.EDU_AFFIL));
user.setAccountCreation(TimeUtils.getNow().getMillis());
UserDetails d = new UserDetails();
d.read(attributes, true);
UserSettings s = new UserSettings();
s.read(attributes, true);
entHandler.createAccount(user);
s.setUserId(user.getId());
d.setUserId(user.getId());
UserDataDbIface dao = BeansFactory.getTypeFactory().getTypeInterfaceBean(userdatadaos, UserDetails.class);
assert dao != null;
dao.store(d);
dao = BeansFactory.getTypeFactory().getTypeInterfaceBean(userdatadaos, UserSettings.class);
assert dao != null;
dao.store(d);
return user;
}
/**
* link shibboleth and korap user account to one another.
*
* @param current
* currently logged in user
* @param for_name
* foreign user name the current account should be linked to
* @param transstrat
* transfer status of user data (details, settings, user queries)
* 0 = the currently logged in data should be kept 1 = the
* foreign account data should be kept
* @throws NotAuthorizedException
* @throws KustvaktException
*/
// todo:
public void accountLink(User current, String for_name, int transstrat) throws KustvaktException {
// User foreign = entHandler.getAccount(for_name);
// if (current.getAccountLink() == null && current.getAccountLink()
// .isEmpty()) {
// if (current instanceof KorAPUser && foreign instanceof ShibUser) {
// if (transstrat == 1)
// current.transfer(foreign);
//// foreign.setAccountLink(current.getUsername());
//// current.setAccountLink(foreign.getUsername());
// // entHandler.purgeDetails(foreign);
// // entHandler.purgeSettings(foreign);
// }else if (foreign instanceof KorAPUser
// && current instanceof ShibUser) {
// if (transstrat == 0)
// foreign.transfer(current);
//// current.setAccountLink(foreign.getUsername());
// // entHandler.purgeDetails(current);
// // entHandler.purgeSettings(current);
// // entHandler.purgeSettings(current);
// }
// entHandler.updateAccount(current);
// entHandler.updateAccount(foreign);
// }
}
// todo: test and rest usage?!
public boolean updateAccount(User user) throws KustvaktException {
boolean result;
if (user instanceof DemoUser)
throw new KustvaktException(user.getId(), StatusCodes.REQUEST_INVALID,
"account not updateable for demo user", user.getUsername());
else {
// crypto.validate(user);
try {
result = entHandler.updateAccount(user) > 0;
} catch (KustvaktException e) {
jlog.error("Error: {}", e.string());
throw new WrappedException(e, StatusCodes.UPDATE_ACCOUNT_FAILED);
}
}
if (result) {
// this.removeCacheEntry(user.getUsername());
auditing.audit(
AuditRecord.serviceRecord(user.getId(), StatusCodes.UPDATE_ACCOUNT_SUCCESSFUL, user.toString()));
}
return result;
}
public boolean deleteAccount(User user) throws KustvaktException {
boolean result;
if (user instanceof DemoUser)
return true;
else {
try {
result = entHandler.deleteAccount(user.getId()) > 0;
} catch (KustvaktException e) {
jlog.error("Error: {}", e.string());
throw new WrappedException(e, StatusCodes.DELETE_ACCOUNT_FAILED);
}
}
if (result) {
// this.removeCacheEntry(user.getUsername());
auditing.audit(AuditRecord.serviceRecord(user.getUsername(), StatusCodes.DELETE_ACCOUNT_SUCCESSFUL,
user.toString()));
}
return result;
}
public Object[] validateResetPasswordRequest(String username, String email) throws KustvaktException {
String uritoken;
validator.validateEntry(email, Attributes.EMAIL);
User ident;
try {
ident = entHandler.getAccount(username);
if (ident instanceof DemoUser)
// throw new
// NotAuthorizedException(StatusCodes.PERMISSION_DENIED,
// "password reset now allowed for DemoUser", "");
throw new WrappedException(username, StatusCodes.PASSWORD_RESET_FAILED, username);
} catch (EmptyResultException e) {
throw new WrappedException(
new KustvaktException(username, StatusCodes.ILLEGAL_ARGUMENT, "username not found", username),
StatusCodes.PASSWORD_RESET_FAILED, username);
}
Userdata data = this.getUserData(ident, UserDetails.class);
KorAPUser user = (KorAPUser) ident;
if (!email.equals(data.get(Attributes.EMAIL)))
// throw new NotAuthorizedException(StatusCodes.ILLEGAL_ARGUMENT,
// "invalid parameter: email", "email");
throw new WrappedException(
new KustvaktException(user.getId(), StatusCodes.ILLEGAL_ARGUMENT, "email invalid", email),
StatusCodes.PASSWORD_RESET_FAILED, email);
uritoken = crypto.encodeBase();
URIParam param = new URIParam(uritoken, TimeUtils.plusHours(24).getMillis());
user.addField(param);
try {
entHandler.updateAccount(user);
} catch (KustvaktException e) {
jlog.error("Error ", e.string());
throw new WrappedException(e, StatusCodes.PASSWORD_RESET_FAILED);
}
return new Object[] { uritoken, TimeUtils.format(param.getUriExpiration()) };
}
// EM: not in the new DB
@Override
public <T extends Userdata> T getUserData(User user, Class<T> clazz) throws WrappedException {
try {
UserDataDbIface<T> dao = BeansFactory.getTypeFactory()
.getTypeInterfaceBean(BeansFactory.getKustvaktContext().getUserDataProviders(), clazz);
T data = null;
if (dao != null)
data = dao.get(user);
if (data == null)
throw new KustvaktException(user.getId(), StatusCodes.NO_RESULT_FOUND, "No data found!",
clazz.getSimpleName());
return data;
} catch (KustvaktException e) {
jlog.error("Error during user data retrieval: {}", e.getEntity());
throw new WrappedException(e, StatusCodes.GET_ACCOUNT_FAILED);
}
}
// todo: cache userdata outside of the user object!
@Override
public void updateUserData(Userdata data) throws WrappedException {
try {
data.validate(this.validator);
UserDataDbIface dao = BeansFactory.getTypeFactory()
.getTypeInterfaceBean(BeansFactory.getKustvaktContext().getUserDataProviders(), data.getClass());
if (dao != null)
dao.update(data);
} catch (KustvaktException e) {
jlog.error("Error during update of user data!", e.getEntity());
throw new WrappedException(e, StatusCodes.UPDATE_ACCOUNT_FAILED);
}
}
}