Commit f6722e9b authored by michael.simon's avatar michael.simon
Browse files

Update OIDC User entity

parent 576bd5c3
......@@ -2,12 +2,14 @@ package edu.kit.scc.webreg.service.oidc.client;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import edu.kit.scc.webreg.service.saml.exc.OidcAuthenticationException;
public interface OidcClientCallbackService extends Serializable {
void callback(String uri, HttpServletResponse response) throws OidcAuthenticationException;
void callback(String uri, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws OidcAuthenticationException;
}
......@@ -3,9 +3,14 @@ package edu.kit.scc.webreg.service.oidc.client;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
......@@ -44,15 +49,21 @@ import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator;
import edu.kit.scc.webreg.bootstrap.ApplicationConfig;
import edu.kit.scc.webreg.dao.UserLoginInfoDao;
import edu.kit.scc.webreg.dao.oidc.OidcRpConfigurationDao;
import edu.kit.scc.webreg.dao.oidc.OidcRpFlowStateDao;
import edu.kit.scc.webreg.dao.oidc.OidcUserDao;
import edu.kit.scc.webreg.drools.impl.KnowledgeSessionSingleton;
import edu.kit.scc.webreg.entity.SamlUserEntity;
import edu.kit.scc.webreg.entity.UserLoginInfoEntity;
import edu.kit.scc.webreg.entity.UserLoginInfoStatus;
import edu.kit.scc.webreg.entity.UserLoginMethod;
import edu.kit.scc.webreg.entity.oidc.OidcRpConfigurationEntity;
import edu.kit.scc.webreg.entity.oidc.OidcRpFlowStateEntity;
import edu.kit.scc.webreg.entity.oidc.OidcUserEntity;
import edu.kit.scc.webreg.exc.UserUpdateException;
import edu.kit.scc.webreg.service.saml.exc.OidcAuthenticationException;
import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException;
import edu.kit.scc.webreg.session.SessionManager;
@Stateless
......@@ -84,11 +95,17 @@ public class OidcClientCallbackServiceImpl implements OidcClientCallbackService
@Inject
private OidcTokenHelper oidcTokenHelper;
@Inject
private OidcUserUpdater userUpdater;
@Inject
private UserLoginInfoDao userLoginInfoDao;
@Inject
private SessionManager session;
@Override
public void callback(String uri, HttpServletResponse httpServletResponse) throws OidcAuthenticationException {
public void callback(String uri, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws OidcAuthenticationException {
try {
AuthorizationResponse response = AuthorizationResponse.parse(new URI(uri));
......@@ -171,7 +188,7 @@ public class OidcClientCallbackServiceImpl implements OidcClientCallbackService
if (user != null) {
MDC.put("userId", "" + user.getId());
}
if (user == null) {
logger.info("New User detected, sending to register Page");
......@@ -183,7 +200,39 @@ public class OidcClientCallbackServiceImpl implements OidcClientCallbackService
httpServletResponse.sendRedirect("/register/register-oidc.xhtml");
return;
}
logger.debug("Updating OIDC user {}", user.getSubjectId());
try {
user = userUpdater.updateUser(user, claims, userInfo, "web-sso");
} catch (UserUpdateException e) {
logger.warn("Could not update user {}: {}", e.getMessage(), user.getEppn());
throw new OidcAuthenticationException(e.getMessage());
}
session.setIdentityId(user.getIdentity().getId());
session.setLoginTime(Instant.now());
session.setTheme(user.getTheme());
session.setLocale(user.getLocale());
UserLoginInfoEntity loginInfo = userLoginInfoDao.createNew();
loginInfo.setUser(user);
loginInfo.setLoginDate(new Date());
loginInfo.setLoginMethod(UserLoginMethod.HOME_ORG);
loginInfo.setLoginStatus(UserLoginInfoStatus.SUCCESS);
loginInfo.setFrom(httpServletRequest.getRemoteAddr());
loginInfo = userLoginInfoDao.persist(loginInfo);
if (session.getOriginalRequestPath() != null) {
String orig = session.getOriginalRequestPath();
session.setOriginalRequestPath(null);
httpServletResponse.sendRedirect(orig);
}
else
httpServletResponse.sendRedirect("/index.xhtml");
return;
} catch (IOException | ParseException | URISyntaxException e) {
logger.warn("Oidc callback failed: {}", e.getMessage());
throw new OidcAuthenticationException(e);
......
......@@ -15,7 +15,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.ejb.Stateless;
import javax.inject.Inject;
......@@ -35,7 +34,6 @@ import edu.kit.scc.webreg.dao.identity.IdentityDao;
import edu.kit.scc.webreg.dao.oidc.OidcRpConfigurationDao;
import edu.kit.scc.webreg.dao.oidc.OidcUserDao;
import edu.kit.scc.webreg.entity.EventType;
import edu.kit.scc.webreg.entity.SamlUserEntity;
import edu.kit.scc.webreg.entity.UserRoleEntity;
import edu.kit.scc.webreg.entity.UserStatus;
import edu.kit.scc.webreg.entity.audit.AuditStatus;
......@@ -46,9 +44,6 @@ import edu.kit.scc.webreg.event.EventSubmitter;
import edu.kit.scc.webreg.event.UserEvent;
import edu.kit.scc.webreg.exc.EventSubmitException;
import edu.kit.scc.webreg.exc.UserUpdateException;
import edu.kit.scc.webreg.service.impl.AttributeMapHelper;
import edu.kit.scc.webreg.service.impl.HomeOrgGroupUpdater;
import edu.kit.scc.webreg.service.impl.UserUpdater;
@Stateless
public class OidcUserCreateServiceImpl implements OidcUserCreateService {
......@@ -69,11 +64,8 @@ public class OidcUserCreateServiceImpl implements OidcUserCreateService {
private OidcTokenHelper tokenHelper;
@Inject
private UserUpdater userUpdater;
private OidcUserUpdater userUpdater;
@Inject
private HomeOrgGroupUpdater homeOrgGroupUpdater;
@Inject
private RoleDao roleDao;
......@@ -89,9 +81,6 @@ public class OidcUserCreateServiceImpl implements OidcUserCreateService {
@Inject
private EventSubmitter eventSubmitter;
@Inject
private AttributeMapHelper attrHelper;
@Inject
private ApplicationConfig appConfig;
......@@ -145,7 +134,7 @@ public class OidcUserCreateServiceImpl implements OidcUserCreateService {
auditor.setName(getClass().getName() + "-OidcUserCreate-Audit");
auditor.setDetail("Create OIDC user " + user.getSubjectId());
// userUpdater.updateUserFromAttribute(user, attributeMap, true, auditor);
userUpdater.updateUserFromAttribute(user, attributeMap, true, auditor);
/**
* if user has no uid number yet, generate one
......
package edu.kit.scc.webreg.service.oidc.client;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.MDC;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
import edu.kit.scc.webreg.audit.Auditor;
import edu.kit.scc.webreg.audit.RegistryAuditor;
import edu.kit.scc.webreg.audit.UserUpdateAuditor;
import edu.kit.scc.webreg.bootstrap.ApplicationConfig;
import edu.kit.scc.webreg.dao.RegistryDao;
import edu.kit.scc.webreg.dao.as.ASUserAttrDao;
import edu.kit.scc.webreg.dao.audit.AuditDetailDao;
import edu.kit.scc.webreg.dao.audit.AuditEntryDao;
import edu.kit.scc.webreg.dao.oidc.OidcUserDao;
import edu.kit.scc.webreg.entity.EventType;
import edu.kit.scc.webreg.entity.RegistryEntity;
import edu.kit.scc.webreg.entity.RegistryStatus;
import edu.kit.scc.webreg.entity.ServiceEntity;
import edu.kit.scc.webreg.entity.UserEntity;
import edu.kit.scc.webreg.entity.UserStatus;
import edu.kit.scc.webreg.entity.as.ASUserAttrEntity;
import edu.kit.scc.webreg.entity.as.AttributeSourceServiceEntity;
import edu.kit.scc.webreg.entity.audit.AuditStatus;
import edu.kit.scc.webreg.entity.oidc.OidcUserEntity;
import edu.kit.scc.webreg.event.EventSubmitter;
import edu.kit.scc.webreg.event.UserEvent;
import edu.kit.scc.webreg.exc.EventSubmitException;
import edu.kit.scc.webreg.exc.RegisterException;
import edu.kit.scc.webreg.exc.UserUpdateException;
import edu.kit.scc.webreg.service.SerialService;
import edu.kit.scc.webreg.service.ServiceService;
import edu.kit.scc.webreg.service.UserServiceHook;
import edu.kit.scc.webreg.service.impl.AttributeMapHelper;
import edu.kit.scc.webreg.service.impl.HookManager;
import edu.kit.scc.webreg.service.reg.AttributeSourceQueryService;
import edu.kit.scc.webreg.service.reg.impl.Registrator;
@ApplicationScoped
public class OidcUserUpdater implements Serializable {
private static final long serialVersionUID = 1L;
@Inject
private Logger logger;
@Inject
private AuditEntryDao auditDao;
@Inject
private AuditDetailDao auditDetailDao;
@Inject
private OidcUserDao userDao;
@Inject
private ServiceService serviceService;
@Inject
private RegistryDao registryDao;
@Inject
private SerialService serialService;
@Inject
private HookManager hookManager;
@Inject
private ASUserAttrDao asUserAttrDao;
@Inject
private AttributeSourceQueryService attributeSourceQueryService;
@Inject
private EventSubmitter eventSubmitter;
@Inject
private ApplicationConfig appConfig;
@Inject
private Registrator registrator;
@Inject
private AttributeMapHelper attrHelper;
@Inject
private OidcTokenHelper oidcTokenHelper;
public OidcUserEntity updateUser(OidcUserEntity user, Map<String, List<Object>> attributeMap, String executor)
throws UserUpdateException {
return updateUser(user, attributeMap, executor, null);
}
public OidcUserEntity updateUser(OidcUserEntity user, Map<String, List<Object>> attributeMap, String executor,
ServiceEntity service)
throws UserUpdateException {
MDC.put("userId", "" + user.getId());
logger.debug("Updating user {}", user.getEppn());
user = userDao.merge(user);
boolean changed = false;
UserUpdateAuditor auditor = new UserUpdateAuditor(auditDao, auditDetailDao, appConfig);
auditor.startAuditTrail(executor);
auditor.setName(getClass().getName() + "-UserUpdate-Audit");
auditor.setDetail("Update OIDC user " + user.getSubjectId());
// List to store parent services, that are not registered. Need to be registered
// later, when attribute map is populated
List<ServiceEntity> delayedRegisterList = new ArrayList<ServiceEntity>();
/**
* put no_assertion_count in generic store if assertion is missing. Else
* reset no assertion count and put last valid assertion date in
*/
if (attributeMap == null) {
if (! user.getGenericStore().containsKey("no_assertion_count")) {
user.getGenericStore().put("no_assertion_count", "1");
}
else {
user.getGenericStore().put("no_assertion_count",
"" + (Long.parseLong(user.getGenericStore().get("no_assertion_count")) + 1L));
}
logger.info("No attribute for user {}, skipping updateFromAttribute", user.getEppn());
user.getAttributeStore().clear();
if (UserStatus.ACTIVE.equals(user.getUserStatus())) {
changeUserStatus(user, UserStatus.ON_HOLD, auditor);
/*
* Also flag all registries for user ON_HOLD
*/
List<RegistryEntity> registryList = registryDao.findByUserAndStatus(user,
RegistryStatus.ACTIVE, RegistryStatus.LOST_ACCESS, RegistryStatus.INVALID);
for (RegistryEntity registry : registryList) {
changeRegistryStatus(registry, RegistryStatus.ON_HOLD, auditor);
}
}
}
else {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
user.getGenericStore().put("no_assertion_count", "0");
user.getGenericStore().put("last_valid_assertion", df.format(new Date()));
changed |= updateUserFromAttribute(user, attributeMap, auditor);
if (UserStatus.ON_HOLD.equals(user.getUserStatus())) {
changeUserStatus(user, UserStatus.ACTIVE, auditor);
/*
* Also reenable all registries for user to LOST_ACCESS.
* They are rechecked then
*/
List<RegistryEntity> registryList = registryDao.findByUserAndStatus(user,
RegistryStatus.ON_HOLD);
for (RegistryEntity registry : registryList) {
changeRegistryStatus(registry, RegistryStatus.LOST_ACCESS, auditor);
/*
* check if parent registry is missing
*/
if (registry.getService().getParentService() != null) {
List<RegistryEntity> parentRegistryList = registryDao.findByServiceAndIdentityAndNotStatus(
registry.getService().getParentService(), user.getIdentity(),
RegistryStatus.DELETED, RegistryStatus.DEPROVISIONED);
if (parentRegistryList.size() == 0) {
delayedRegisterList.add(registry.getService().getParentService());
}
}
}
/*
* fire a user changed event to be sure, when the user is activated
*/
changed = true;
}
/*
* if service is set, update only attribute sources spcific for this
* service. Else update all (login via web or generic attribute query)
*/
if (service != null) {
service = serviceService.findByIdWithAttrs(service.getId(), "attributeSourceService");
for (AttributeSourceServiceEntity asse : service.getAttributeSourceService()) {
changed |= attributeSourceQueryService.updateUserAttributes(user, asse.getAttributeSource(), executor);
}
}
else {
List<ASUserAttrEntity> asUserAttrList = asUserAttrDao.findForUser(user);
for (ASUserAttrEntity asUserAttr : asUserAttrList) {
changed |= attributeSourceQueryService.updateUserAttributes(user, asUserAttr.getAttributeSource(), executor);
}
}
Map<String, String> attributeStore = user.getAttributeStore();
attributeStore.clear();
for (Entry<String, List<Object>> entry : attributeMap.entrySet()) {
attributeStore.put(entry.getKey(), attrHelper.attributeListToString(entry.getValue()));
}
}
for (ServiceEntity delayedService : delayedRegisterList) {
try {
registrator.registerUser(user, delayedService,
"user-" + user.getId(), false);
} catch (RegisterException e) {
logger.warn("Parent registrytion didn't work out like it should", e);
}
}
user.setLastUpdate(new Date());
user.setLastFailedUpdate(null);
if (changed) {
fireUserChangeEvent(user, auditor.getActualExecutor(), auditor);
}
auditor.setUser(user);
auditor.finishAuditTrail();
auditor.commitAuditTrail();
return user;
}
public OidcUserEntity updateUser(OidcUserEntity user, IDTokenClaimsSet claims, UserInfo userInfo, String executor, ServiceEntity service)
throws UserUpdateException {
Map<String, List<Object>> attributeMap = oidcTokenHelper.convertToAttributeMap(claims, userInfo);
if (service != null)
return updateUser(user, attributeMap, executor, service);
else
return updateUser(user, attributeMap, executor);
}
public OidcUserEntity updateUser(OidcUserEntity user, IDTokenClaimsSet claims, UserInfo userInfo, String executor)
throws UserUpdateException {
return updateUser(user, claims, userInfo, executor, null);
}
protected void fireUserChangeEvent(UserEntity user, String executor, Auditor auditor) {
UserEvent userEvent = new UserEvent(user, auditor.getAudit());
try {
eventSubmitter.submit(userEvent, EventType.USER_UPDATE, executor);
} catch (EventSubmitException e) {
logger.warn("Could not submit event", e);
}
}
public boolean updateUserFromAttribute(UserEntity user, Map<String, List<Object>> attributeMap, Auditor auditor)
throws UserUpdateException {
return updateUserFromAttribute(user, attributeMap, false, auditor);
}
public boolean updateUserFromAttribute(UserEntity user, Map<String, List<Object>> attributeMap, boolean withoutUidNumber, Auditor auditor)
throws UserUpdateException {
boolean changed = false;
UserServiceHook completeOverrideHook = null;
Set<UserServiceHook> activeHooks = new HashSet<UserServiceHook>();
for (UserServiceHook hook : hookManager.getUserHooks()) {
if (hook.isResponsible(user, attributeMap)) {
hook.preUpdateUserFromAttribute(user, attributeMap, auditor);
activeHooks.add(hook);
if (hook.isCompleteOverride()) {
completeOverrideHook = hook;
}
}
}
if (completeOverrideHook == null) {
changed |= compareAndChangeProperty(user, "email", attributeMap.get("urn:oid:0.9.2342.19200300.100.1.3"), auditor);
changed |= compareAndChangeProperty(user, "eppn", attributeMap.get("urn:oid:1.3.6.1.4.1.5923.1.1.1.6"), auditor);
changed |= compareAndChangeProperty(user, "givenName", attributeMap.get("urn:oid:2.5.4.42"), auditor);
changed |= compareAndChangeProperty(user, "surName", attributeMap.get("urn:oid:2.5.4.4"), auditor);
List<String> emailList = attrHelper.attributeListToStringList(attributeMap, "urn:oid:0.9.2342.19200300.100.1.3");
if (emailList != null && emailList.size() > 1) {
if (user.getEmailAddresses() == null) {
user.setEmailAddresses(new HashSet<String>());
}
for (int i=1; i<emailList.size(); i++) {
user.getEmailAddresses().add(emailList.get(i));
}
}
if ((! withoutUidNumber) && (user.getUidNumber() == null)) {
user.setUidNumber(serialService.next("uid-number-serial").intValue());
logger.info("Setting UID Number {} for user {}", user.getUidNumber(), user.getEppn());
auditor.logAction(user.getEppn(), "SET FIELD", "uidNumber", "" + user.getUidNumber(), AuditStatus.SUCCESS);
changed = true;
}
}
else {
logger.info("Overriding standard User Update Mechanism! Activator: {}", completeOverrideHook.getClass().getName());
}
for (UserServiceHook hook : activeHooks) {
hook.postUpdateUserFromAttribute(user, attributeMap, auditor);
}
return changed;
}
private boolean compareAndChangeProperty(UserEntity user, String property, List<Object> objectValue, Auditor auditor) {
String s = null;
String action = null;
// In case of a List (multiple SAML Values), take the first value
String value = attrHelper.getSingleStringFirst(objectValue);
try {
Object actualValue = PropertyUtils.getProperty(user, property);
if (actualValue != null && actualValue.equals(value)) {
// Value didn't change, do nothing
return false;
}
if (actualValue == null && value == null) {
// Value stayed null
return false;
}
if (actualValue == null) {
s = "null";
action = "SET FIELD";
}
else {
s = actualValue.toString();
action = "UPDATE FIELD";
}
s = s + " -> " + value;
if (s.length() > 1017) s = s.substring(0, 1017) + "...";
PropertyUtils.setProperty(user, property, value);
auditor.logAction(user.getEppn(), action, property, s, AuditStatus.SUCCESS);
} catch (IllegalAccessException e) {
logger.warn("This probably shouldn't happen: ", e);
auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL);
} catch (InvocationTargetException e) {
logger.warn("This probably shouldn't happen: ", e);
auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL);
} catch (NoSuchMethodException e) {
logger.warn("This probably shouldn't happen: ", e);
auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL);
}
return true;
}
protected void changeUserStatus(UserEntity user, UserStatus toStatus, Auditor auditor) {
UserStatus fromStatus = user.getUserStatus();
user.setUserStatus(toStatus);
user.setLastStatusChange(new Date());
logger.debug("{}: change user status from {} to {}", user.getEppn(), fromStatus, toStatus);
auditor.logAction(user.getEppn(), "CHANGE STATUS", fromStatus + " -> " + toStatus,
"Change status " + fromStatus + " -> " + toStatus, AuditStatus.SUCCESS);