/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.keycloak.saml; import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.dom.saml.v2.assertion.AudienceRestrictionType; import org.keycloak.dom.saml.v2.assertion.AuthnStatementType; import org.keycloak.dom.saml.v2.assertion.ConditionsType; import org.keycloak.dom.saml.v2.assertion.OneTimeUseType; import org.keycloak.dom.saml.v2.assertion.SubjectConfirmationDataType; import org.keycloak.dom.saml.v2.protocol.ExtensionsType; import org.keycloak.dom.saml.v2.protocol.ResponseType; import org.keycloak.saml.common.PicketLinkLogger; import org.keycloak.saml.common.PicketLinkLoggerFactory; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.exceptions.ConfigurationException; import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.common.util.DocumentUtil; import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response; import org.keycloak.saml.processing.core.saml.v2.common.IDGenerator; import org.keycloak.saml.processing.core.saml.v2.holders.IDPInfoHolder; import org.keycloak.saml.processing.core.saml.v2.holders.IssuerInfoHolder; import org.keycloak.saml.processing.core.saml.v2.holders.SPInfoHolder; import org.keycloak.saml.processing.core.saml.v2.util.StatementUtil; import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil; import org.w3c.dom.Document; import java.net.URI; import java.util.LinkedList; import java.util.List; import static org.keycloak.saml.common.util.StringUtil.isNotNull; /** * <p> Handles for dealing with SAML2 Authentication </p> * <p/> * Configuration Options: * * @author bburke@redhat.com */ public class SAML2LoginResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LoginResponseBuilder> { protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger(); protected String destination; protected String issuer; protected int subjectExpiration; protected int assertionExpiration; protected String nameId; protected String nameIdFormat; protected boolean multiValuedRoles; protected boolean disableAuthnStatement; protected String requestID; protected String authMethod; protected String requestIssuer; protected String sessionIndex; protected final List<NodeGenerator> extensions = new LinkedList<>(); protected boolean includeOneTimeUseCondition; public SAML2LoginResponseBuilder sessionIndex(String sessionIndex) { this.sessionIndex = sessionIndex; return this; } public SAML2LoginResponseBuilder destination(String destination) { this.destination = destination; return this; } public SAML2LoginResponseBuilder issuer(String issuer) { this.issuer = issuer; return this; } /** * Length of time in seconds the subject can be confirmed * See SAML core specification 2.4.1.2 NotOnOrAfter * * @param subjectExpiration Number of seconds the subject should be valid * @return */ public SAML2LoginResponseBuilder subjectExpiration(int subjectExpiration) { this.subjectExpiration = subjectExpiration; return this; } /** * Length of time in seconds the assertion is valid for * See SAML core specification 2.5.1.2 NotOnOrAfter * * @param assertionExpiration Number of seconds the assertion should be valid * @return */ public SAML2LoginResponseBuilder assertionExpiration(int assertionExpiration) { this.assertionExpiration = assertionExpiration; return this; } public SAML2LoginResponseBuilder requestID(String requestID) { this.requestID = requestID; return this; } public SAML2LoginResponseBuilder requestIssuer(String requestIssuer) { this.requestIssuer = requestIssuer; return this; } public SAML2LoginResponseBuilder authMethod(String authMethod) { this.authMethod = authMethod; return this; } public SAML2LoginResponseBuilder nameIdentifier(String nameIdFormat, String nameId) { this.nameIdFormat = nameIdFormat; this.nameId = nameId; return this; } public SAML2LoginResponseBuilder multiValuedRoles(boolean multiValuedRoles) { this.multiValuedRoles = multiValuedRoles; return this; } public SAML2LoginResponseBuilder disableAuthnStatement(boolean disableAuthnStatement) { this.disableAuthnStatement = disableAuthnStatement; return this; } public SAML2LoginResponseBuilder includeOneTimeUseCondition(boolean includeOneTimeUseCondition) { this.includeOneTimeUseCondition = includeOneTimeUseCondition; return this; } @Override public SAML2LoginResponseBuilder addExtension(NodeGenerator extension) { this.extensions.add(extension); return this; } public Document buildDocument(ResponseType responseType) throws ConfigurationException, ProcessingException { Document samlResponseDocument = null; try { SAML2Response docGen = new SAML2Response(); samlResponseDocument = docGen.convert(responseType); if (logger.isTraceEnabled()) { logger.trace("SAML Response Document: " + DocumentUtil.asString(samlResponseDocument)); } } catch (Exception e) { throw logger.samlAssertionMarshallError(e); } return samlResponseDocument; } public ResponseType buildModel() throws ConfigurationException, ProcessingException { ResponseType responseType = null; SAML2Response saml2Response = new SAML2Response(); // Create a response type String id = IDGenerator.create("ID_"); IssuerInfoHolder issuerHolder = new IssuerInfoHolder(issuer); issuerHolder.setStatusCode(JBossSAMLURIConstants.STATUS_SUCCESS.get()); IDPInfoHolder idp = new IDPInfoHolder(); idp.setNameIDFormatValue(nameId); idp.setNameIDFormat(nameIdFormat); SPInfoHolder sp = new SPInfoHolder(); sp.setResponseDestinationURI(destination); sp.setRequestID(requestID); sp.setIssuer(requestIssuer); responseType = saml2Response.createResponseType(id, sp, idp, issuerHolder); AssertionType assertion = responseType.getAssertions().get(0).getAssertion(); //Add request issuer as the audience restriction AudienceRestrictionType audience = new AudienceRestrictionType(); audience.addAudience(URI.create(requestIssuer)); assertion.getConditions().addCondition(audience); //Update Conditions NotOnOrAfter if(assertionExpiration > 0) { ConditionsType conditions = assertion.getConditions(); conditions.setNotOnOrAfter(XMLTimeUtil.add(conditions.getNotBefore(), assertionExpiration * 1000)); } //Update SubjectConfirmationData NotOnOrAfter if(subjectExpiration > 0) { SubjectConfirmationDataType subjectConfirmationData = assertion.getSubject().getConfirmation().get(0).getSubjectConfirmationData(); subjectConfirmationData.setNotOnOrAfter(XMLTimeUtil.add(assertion.getConditions().getNotBefore(), subjectExpiration * 1000)); } // Create an AuthnStatementType if (!disableAuthnStatement) { String authContextRef = JBossSAMLURIConstants.AC_UNSPECIFIED.get(); if (isNotNull(authMethod)) authContextRef = authMethod; AuthnStatementType authnStatement = StatementUtil.createAuthnStatement(XMLTimeUtil.getIssueInstant(), authContextRef); if (sessionIndex != null) authnStatement.setSessionIndex(sessionIndex); else authnStatement.setSessionIndex(assertion.getID()); assertion.addStatement(authnStatement); } if (includeOneTimeUseCondition) { assertion.getConditions().addCondition(new OneTimeUseType()); } if (!this.extensions.isEmpty()) { ExtensionsType extensionsType = new ExtensionsType(); for (NodeGenerator extension : this.extensions) { extensionsType.addExtension(extension); } responseType.setExtensions(extensionsType); } return responseType; } }