Commit 6e0926b8 authored by michael.simon's avatar michael.simon
Browse files

add debug saml login process capablilities

parent 69e9c6ab
......@@ -559,7 +559,7 @@ public class UserLoginServiceImpl implements UserLoginService, Serializable {
Assertion assertion = saml2AssertionService.processSamlResponse(samlResponse, idp, idpEntityDescriptor, spEntity);
String persistentId = saml2AssertionService.extractPersistentId(assertion, spEntity);
String persistentId = saml2AssertionService.extractPersistentId(assertion, spEntity, null);
SamlUserEntity user = samlUserDao.findByPersistent(spEntity.getEntityId(),
idp.getEntityId(), persistentId);
......
......@@ -131,7 +131,7 @@ public class Saml2AssertionService {
return assertion;
}
public String extractPersistentId(Assertion assertion, SamlSpConfigurationEntity spEntity)
public String extractPersistentId(Assertion assertion, SamlSpConfigurationEntity spEntity, StringBuffer debugLog)
throws IOException, DecryptionException, SamlAuthenticationException {
logger.debug("Fetching name Id from assertion");
String persistentId;
......@@ -139,40 +139,62 @@ public class Saml2AssertionService {
/*
* Assertion needs a Subject and a NameID, encrypted or not
*/
if (assertion.getSubject() == null)
if (assertion.getSubject() == null) {
if (debugLog != null)
debugLog.append("Subject in Assertion is missing. Cannot process assertion without subject.\n");
throw new SamlAuthenticationException("No Subject in assertion!");
}
if (assertion.getSubject().getNameID() == null &&
assertion.getSubject().getEncryptedID() == null)
assertion.getSubject().getEncryptedID() == null) {
if (debugLog != null)
debugLog.append("NameID in Assertion/Subject is missing. Cannot process assertion without NameID.\n");
throw new SamlAuthenticationException("SAML2 NameID is missing.");
}
/*
* If the NameID is encrypted, decrypt it
*/
NameID nid;
if (assertion.getSubject().getEncryptedID() != null) {
if (debugLog != null)
debugLog.append("NameID is encrypted Decrypting NameID...\n");
EncryptedID eid = assertion.getSubject().getEncryptedID();
SAMLObject samlObject = decryptNameID(eid, spEntity.getCertificate(), spEntity.getPrivateKey(),
spEntity.getStandbyCertificate(), spEntity.getStandbyPrivateKey());
if (samlObject instanceof NameID)
nid = (NameID) samlObject;
else
else {
if (debugLog != null)
debugLog.append("Only Encrypted NameIDs are supoorted. Encrypted BaseIDs or embedded Assertions are not supported.\n")
.append("NameID Type is: ").append(samlObject.getClass().getName()).append("\n");
throw new SamlAuthenticationException("Only Encrypted NameIDs are supoorted. Encrypted BaseIDs or embedded Assertions are not supported");
}
}
else
nid = assertion.getSubject().getNameID();
if (debugLog != null)
debugLog.append("Resulting NameID (XML):\n").append(samlHelper.prettyPrint(nid)).append("\n");
logger.debug("NameId format {} value {}", nid.getFormat(), nid.getValue());
if (nid.getFormat().equals(NameID.TRANSIENT)) {
if (debugLog != null)
debugLog.append("NameID is Transient. At the moment, only Persistent NameID is supported\n");
throw new SamlAuthenticationException("NameID is Transient but must be Persistent");
}
else if (nid.getFormat().equals(NameID.PERSISTENT)) {
persistentId = nid.getValue();
}
else
else {
if (debugLog != null)
debugLog.append("NameID is Unknown type (").append(nid.getClass().getName())
.append("). At the moment, only Persistent NameID is supported\n");
throw new SamlAuthenticationException("Unsupported SAML2 NameID Type");
}
return persistentId;
}
......
......@@ -7,8 +7,9 @@ import edu.kit.scc.webreg.entity.SamlSpConfigurationEntity;
public interface SamlSpPostService {
void consumePost(HttpServletRequest request, HttpServletResponse response, SamlSpConfigurationEntity spConfig)
throws Exception;
void consumePost(HttpServletRequest request, HttpServletResponse response, SamlSpConfigurationEntity spConfig,
StringBuffer debugLog)
throws Exception;
......
......@@ -75,105 +75,135 @@ public class SamlSpPostServiceImpl implements SamlSpPostService {
@Override
public void consumePost(HttpServletRequest request, HttpServletResponse response,
SamlSpConfigurationEntity spConfig) throws Exception {
try {
SamlIdpMetadataEntity idpEntity = idpDao.findById(session.getIdpId());
EntityDescriptor idpEntityDescriptor = samlHelper.unmarshal(
idpEntity.getEntityDescriptor(), EntityDescriptor.class);
SamlSpConfigurationEntity spConfig, StringBuffer debugLog) throws Exception {
Assertion assertion;
String persistentId;
try {
Response samlResponse = saml2DecoderService.decodePostMessage(request);
if (logger.isTraceEnabled())
logger.trace("{}", samlHelper.prettyPrint(samlResponse));
assertion = saml2AssertionService.processSamlResponse(samlResponse, idpEntity, idpEntityDescriptor, spConfig);
if (logger.isTraceEnabled())
logger.trace("{}", samlHelper.prettyPrint(assertion));
persistentId = saml2AssertionService.extractPersistentId(assertion, spConfig);
} catch (Exception e1) {
/*
* Catch Exception here for a probably faulty IDP. Register Exception and rethrow.
*/
if (! SamlIdpMetadataEntityStatus.FAULTY.equals(idpEntity.getIdIdpStatus())) {
idpEntity.setIdIdpStatus(SamlIdpMetadataEntityStatus.FAULTY);
idpEntity.setLastIdStatusChange(new Date());
}
throw e1;
SamlIdpMetadataEntity idpEntity = idpDao.findById(session.getIdpId());
EntityDescriptor idpEntityDescriptor = samlHelper.unmarshal(
idpEntity.getEntityDescriptor(), EntityDescriptor.class);
if (debugLog != null) {
debugLog.append("Resolved IDP for login: ").append(idpEntity.getEntityId()).append("\n");
}
Assertion assertion = null;
Response samlResponse = null;
String persistentId;
try {
if (debugLog != null) {
debugLog.append("Decoding SAML Response...\n");
}
samlResponse = saml2DecoderService.decodePostMessage(request);
if (logger.isTraceEnabled())
logger.trace("{}", samlHelper.prettyPrint(samlResponse));
if (debugLog != null) {
debugLog.append("Decoding SAML Assertion...\n");
}
assertion = saml2AssertionService.processSamlResponse(samlResponse, idpEntity, idpEntityDescriptor, spConfig);
if (! SamlIdpMetadataEntityStatus.GOOD.equals(idpEntity.getIdIdpStatus())) {
idpEntity.setIdIdpStatus(SamlIdpMetadataEntityStatus.GOOD);
if (logger.isTraceEnabled())
logger.trace("{}", samlHelper.prettyPrint(assertion));
if (debugLog != null) {
debugLog.append("Extract Persistent NameID...\n");
}
persistentId = saml2AssertionService.extractPersistentId(assertion, spConfig, debugLog);
if (debugLog != null) {
debugLog.append("Resulting Persistent NameID: ").append(persistentId).append("\n");
}
} catch (Exception e1) {
/*
* Catch Exception here for a probably faulty IDP. Register Exception and rethrow.
*/
if (! SamlIdpMetadataEntityStatus.FAULTY.equals(idpEntity.getIdIdpStatus())) {
idpEntity.setIdIdpStatus(SamlIdpMetadataEntityStatus.FAULTY);
idpEntity.setLastIdStatusChange(new Date());
}
throw e1;
} finally {
if (debugLog != null) {
if (samlResponse != null) {
debugLog.append("\n\nSAML Response:\n\n")
.append(samlHelper.prettyPrint(samlResponse));
}
if (assertion != null) {
debugLog.append("\n\nSAML Assertion:\n\n")
.append(samlHelper.prettyPrint(assertion));
}
}
}
if (! SamlIdpMetadataEntityStatus.GOOD.equals(idpEntity.getIdIdpStatus())) {
idpEntity.setIdIdpStatus(SamlIdpMetadataEntityStatus.GOOD);
idpEntity.setLastIdStatusChange(new Date());
}
Map<String, List<Object>> attributeMap = saml2AssertionService.extractAttributes(assertion);
Map<String, List<Object>> attributeMap = saml2AssertionService.extractAttributes(assertion);
SamlUserEntity user = userDao.findByPersistent(spConfig.getEntityId(),
idpEntity.getEntityId(), persistentId);
SamlUserEntity user = userDao.findByPersistent(spConfig.getEntityId(),
idpEntity.getEntityId(), persistentId);
if (user != null) {
MDC.put("userId", "" + user.getId());
if (user != null) {
MDC.put("userId", "" + user.getId());
}
if (user == null) {
logger.info("New User detected, sending to register Page");
// Store SAML Data temporarily in Session
logger.debug("Storing relevant SAML data in session");
session.setPersistentId(persistentId);
session.setAttributeMap(attributeMap);
if (debugLog != null) {
request.setAttribute("_debugLogExtraRedirect", "/register/register.xhtml");
return;
}
if (user == null) {
logger.info("New User detected, sending to register Page");
// Store SAML Data temporarily in Session
logger.debug("Storing relevant SAML data in session");
session.setPersistentId(persistentId);
session.setAttributeMap(attributeMap);
else {
response.sendRedirect("/register/register.xhtml");
return;
}
logger.debug("Updating user {}", persistentId);
try {
user = userUpdater.updateUser(user, assertion, "web-sso");
} catch (UserUpdateException e) {
logger.warn("Could not update user {}: {}", e.getMessage(), user.getEppn());
throw new SamlAuthenticationException(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(request.getRemoteAddr());
loginInfo = userLoginInfoDao.persist(loginInfo);
if (session.getOriginalRequestPath() != null) {
String orig = session.getOriginalRequestPath();
session.setOriginalRequestPath(null);
response.sendRedirect(orig);
}
else
response.sendRedirect("/index.xhtml");
}
logger.debug("Updating user {}", persistentId);
try {
user = userUpdater.updateUser(user, assertion, "web-sso");
} catch (UserUpdateException e) {
logger.warn("Could not update user {}: {}", e.getMessage(), user.getEppn());
throw new SamlAuthenticationException(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(request.getRemoteAddr());
loginInfo = userLoginInfoDao.persist(loginInfo);
if (debugLog != null) {
return;
} catch (MessageDecodingException e) {
throw new ServletException("Authentication problem", e);
} catch (SecurityException e) {
throw new ServletException("Authentication problem", e);
} catch (DecryptionException e) {
throw new ServletException("Authentication problem", e);
} catch (SamlAuthenticationException e) {
throw new ServletException("Authentication problem", e);
} catch (ComponentInitializationException e) {
throw new ServletException("Authentication problem", e);
}
}
else if (session.getOriginalRequestPath() != null) {
String orig = session.getOriginalRequestPath();
session.setOriginalRequestPath(null);
response.sendRedirect(orig);
}
else
response.sendRedirect("/index.xhtml");
}
}
/*******************************************************************************
* Copyright (c) 2014 Michael Simon.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Michael Simon - initial
******************************************************************************/
package edu.kit.scc.webreg.bean.idpadmn;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import javax.faces.view.ViewScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ComponentSystemEvent;
import javax.inject.Inject;
import org.apache.commons.codec.binary.Base64;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml.saml2.metadata.KeyDescriptor;
import org.opensaml.xmlsec.signature.KeyInfo;
import org.opensaml.xmlsec.signature.X509Certificate;
import org.opensaml.xmlsec.signature.X509Data;
import org.slf4j.Logger;
import edu.kit.scc.webreg.entity.SamlIdpMetadataEntity;
import edu.kit.scc.webreg.entity.SamlUserEntity;
import edu.kit.scc.webreg.entity.UserEntity;
import edu.kit.scc.webreg.entity.identity.IdentityEntity;
import edu.kit.scc.webreg.exc.UserUpdateException;
import edu.kit.scc.webreg.service.RoleService;
import edu.kit.scc.webreg.service.SamlIdpMetadataService;
import edu.kit.scc.webreg.service.UserService;
import edu.kit.scc.webreg.service.identity.IdentityService;
import edu.kit.scc.webreg.service.saml.SamlHelper;
import edu.kit.scc.webreg.session.SessionManager;
import edu.kit.scc.webreg.util.FacesMessageGenerator;
@Named
@ViewScoped
public class IdpDebugLoginBean implements Serializable {
private static final long serialVersionUID = 1L;
@Inject
private Logger logger;
@Inject
private FacesMessageGenerator messageGenerator;
@Inject
private SessionManager session;
private String debugLog;
private String extraRedirect;
private HttpServletRequest request;
public void preRenderView(ComponentSystemEvent ev) {
if (FacesContext.getCurrentInstance().getExternalContext().getRequest() instanceof HttpServletRequest) {
request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
if (request.getAttribute("_debugLog") != null) {
debugLog = (String) request.getAttribute("_debugLog");
}
if (request.getAttribute("_debugLogExtraRedirect") != null) {
extraRedirect = (String) request.getAttribute("_debugLogExtraRedirect");
}
}
}
public void retryLogin() {
try {
FacesContext.getCurrentInstance().getExternalContext().redirect("/logout/local?redirect=idp_debug_login");
} catch (IOException e) {
logger.warn("Coud not redirect to page /logout/local?redirect=idp_debug_login: {}", e.getMessage());
}
}
public String getDebugLog() {
return debugLog;
}
public String getExtraRedirect() {
return extraRedirect;
}
}
......@@ -63,6 +63,8 @@ public class LogoutServlet implements Servlet {
response.sendRedirect(ViewIds.DELETE_ALL_PERSONAL_DATA_DONE);
else if (redirect.equalsIgnoreCase("local_logout"))
response.sendRedirect(ViewIds.LOCAL_LOGOUT_DONE);
else if (redirect.equalsIgnoreCase("idp_debug_login"))
response.sendRedirect("/idp-debug-login/");
else
response.sendRedirect(ViewIds.INDEX_USER);
}
......
......@@ -46,13 +46,36 @@ public class Saml2PostHandler {
return;
}
StringBuffer debugLog = null;
if (session.getOriginalRequestPath() != null && session.getOriginalRequestPath().startsWith("/idp-debug-login/")) {
debugLog = new StringBuffer();
debugLog.append("Starting debug log for login process...\n");
logger.debug("attempAuthentication, Client debug is on");
}
logger.debug("attempAuthentication, Consuming SAML Assertion");
try {
spPostService.consumePost(request, response, spConfig);
spPostService.consumePost(request, response, spConfig, debugLog);
if (debugLog != null) {
request.setAttribute("_debugLog", debugLog.toString());
request.getServletContext().getRequestDispatcher("/idp-debug-login/").forward(request, response);
}
} catch (Exception e) {
throw new ServletException("Authentication problem", e);
if (debugLog != null) {
debugLog.append("Exception: ").append(e.getMessage()).append("\n");
if (e.getCause() != null) {
debugLog.append("Cause: ").append(e.getMessage()).append("\n");
}
request.setAttribute("_debugLog", debugLog.toString());
request.getServletContext().getRequestDispatcher("/idp-debug-login/").forward(request, response);
}
else {
throw new ServletException("Authentication problem", e);
}
}
}
}
......@@ -136,6 +136,9 @@ public class SecurityFilter implements Filter {
else if (path.startsWith("/register/") && session != null && session.getIdentityId() == null) {
chain.doFilter(servletRequest, servletResponse);
}
else if (path.startsWith("/idp-debug-login/") && httpSession != null) {
chain.doFilter(servletRequest, servletResponse);
}
else if (session != null && session.isLoggedIn()) {
MDC.put("userId", "" + session.getIdentityId());
......
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:bw="http://www.scc.kit.edu/bwfacelets"
xmlns:p="http://primefaces.org/ui"
xmlns:of="http://omnifaces.org/functions">
<h:head>
<f:facet name="first">
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#009682" />
<meta http-equiv="cleartype" content="on" />
<title>IDP Debug Login Process</title>
</f:facet>
<h:outputScript library="javax.faces" name="jsf.js" target="head"/>
</h:head>
<h:body>
<f:view>
<f:metadata>
<f:event type="javax.faces.event.PreRenderViewEvent"
listener="#{idpDebugLoginBean.preRenderView}" />
</f:metadata>
<h:form id="form" class="full form">
<h2>IDP Debug Login Process</h2>
<p:messages id="messageBox" showDetail="true" escape="false" />
<p:outputPanel>
<p:commandButton action="#{idpDebugLoginBean.retryLogin()}" value="Redo Login" />
</p:outputPanel>
<p:outputPanel rendered="#{empty idpDebugLoginBean.extraRedirect}">
<a href="/index.xhtml">To the index page</a>
</p:outputPanel>
<p:outputPanel rendered="#{not empty idpDebugLoginBean.extraRedirect}">
<a href="#{idpDebugLoginBean.extraRedirect}">New user. Continue to register page</a>
</p:outputPanel>
<p:outputPanel rendered="#{empty idpDebugLoginBean.debugLog}">
This page only works directly after the login process.
</p:outputPanel>
<p:outputPanel rendered="#{not empty idpDebugLoginBean.debugLog}">
<pre><h:outputText value="#{idpDebugLoginBean.debugLog}" style="font-size: 0.8rem;"/></pre>
</p:outputPanel>
</h:form>
</f:view>
</h:body>
</html>
......@@ -27,6 +27,10 @@
<h3><h:outputText value="#{messages.welcome}"/></h3>
<p:panel>
<p:panel rendered="#{sessionManager.originalRequestPath != null and sessionManager.originalRequestPath.startsWith('/idp-debug-login/')}">
<span style="color:red;">Login debugging is on. You will see a full log of your login process.</span>
</p:panel>
<p:outputPanel rendered="#{empty discoveryLoginBean.spMetadata and empty discoveryLoginBean.clientConfig}" style="margin-top: 8px;">
<h:outputText value="#{messages.welcome_disco}" escape="false"/>
</p:outputPanel>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment