Merge pull request #852 from eivim/master
Add SAML NameID Formats and include certificate in signature
This commit is contained in:
commit
43cb52b319
4 changed files with 65 additions and 12 deletions
|
@ -38,7 +38,8 @@ public class SALM2LoginResponseBuilder extends SAML2BindingBuilder<SALM2LoginRes
|
|||
protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
|
||||
|
||||
protected List<String> roles = new LinkedList<String>();
|
||||
protected String userPrincipal;
|
||||
protected String nameId;
|
||||
protected String nameIdFormat;
|
||||
protected boolean multiValuedRoles;
|
||||
protected boolean disableAuthnStatement;
|
||||
protected String requestID;
|
||||
|
@ -88,8 +89,9 @@ public class SALM2LoginResponseBuilder extends SAML2BindingBuilder<SALM2LoginRes
|
|||
return this;
|
||||
}
|
||||
|
||||
public SALM2LoginResponseBuilder userPrincipal(String userPrincipal) {
|
||||
this.userPrincipal = userPrincipal;
|
||||
public SALM2LoginResponseBuilder nameIdentifier(String nameIdFormat, String nameId) {
|
||||
this.nameIdFormat = nameIdFormat;
|
||||
this.nameId = nameId;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -129,8 +131,8 @@ public class SALM2LoginResponseBuilder extends SAML2BindingBuilder<SALM2LoginRes
|
|||
issuerHolder.setStatusCode(JBossSAMLURIConstants.STATUS_SUCCESS.get());
|
||||
|
||||
IDPInfoHolder idp = new IDPInfoHolder();
|
||||
idp.setNameIDFormatValue(userPrincipal);
|
||||
idp.setNameIDFormat(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get());
|
||||
idp.setNameIDFormatValue(nameId);
|
||||
idp.setNameIDFormat(nameIdFormat);
|
||||
|
||||
SPInfoHolder sp = new SPInfoHolder();
|
||||
sp.setResponseDestinationURI(destination);
|
||||
|
|
|
@ -54,6 +54,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
public static final String SAML_ENCRYPT = "saml.encrypt";
|
||||
public static final String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
|
||||
public static final String SAML_REQUEST_ID = "SAML_REQUEST_ID";
|
||||
public static final String SAML_DEFAULT_NAMEID_FORMAT = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get();
|
||||
|
||||
protected KeycloakSession session;
|
||||
|
||||
|
@ -117,6 +118,23 @@ public class SamlProtocol implements LoginProtocol {
|
|||
return SamlProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SamlProtocol.SAML_BINDING)) || "true".equals(client.getAttribute(SAML_FORCE_POST_BINDING));
|
||||
}
|
||||
|
||||
protected String getNameIdFormat(ClientSessionModel clientSession) {
|
||||
String nameIdFormat = clientSession.getNote(GeneralConstants.NAMEID_FORMAT);
|
||||
if(nameIdFormat == null) return SAML_DEFAULT_NAMEID_FORMAT;
|
||||
return nameIdFormat;
|
||||
}
|
||||
|
||||
protected String getNameId(String nameIdFormat, ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||
if(nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
|
||||
return userSession.getUser().getEmail();
|
||||
} else if(nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get())) {
|
||||
return clientSession.getNote(ClientSessionCode.ACTION_KEY);
|
||||
} else {
|
||||
// TODO: Support for persistent NameID (pseudo-random identifier persisted in user object)
|
||||
return userSession.getUser().getUsername();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
|
@ -125,6 +143,8 @@ public class SamlProtocol implements LoginProtocol {
|
|||
String relayState = clientSession.getNote(GeneralConstants.RELAY_STATE);
|
||||
String redirectUri = clientSession.getRedirectUri();
|
||||
String responseIssuer = getResponseIssuer(realm);
|
||||
String nameIdFormat = getNameIdFormat(clientSession);
|
||||
String nameId = getNameId(nameIdFormat, clientSession, userSession);
|
||||
|
||||
SALM2LoginResponseBuilder builder = new SALM2LoginResponseBuilder();
|
||||
builder.requestID(requestID)
|
||||
|
@ -132,8 +152,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
.destination(redirectUri)
|
||||
.responseIssuer(responseIssuer)
|
||||
.requestIssuer(clientSession.getClient().getClientId())
|
||||
.userPrincipal(userSession.getUser().getUsername()) // todo userId instead? There is no username claim it seems
|
||||
.attribute(X500SAMLProfileConstants.USERID.getFriendlyName(), userSession.getUser().getId())
|
||||
.nameIdentifier(nameIdFormat, nameId)
|
||||
.authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
|
||||
initClaims(builder, clientSession.getClient(), userSession.getUser());
|
||||
if (clientSession.getRoles() != null) {
|
||||
|
@ -148,12 +167,12 @@ public class SamlProtocol implements LoginProtocol {
|
|||
}
|
||||
if (requiresRealmSignature(client)) {
|
||||
builder.signatureAlgorithm(getSignatureAlgorithm(client))
|
||||
.signWith(realm.getPrivateKey(), realm.getPublicKey())
|
||||
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||
.signDocument();
|
||||
}
|
||||
if (requiresAssertionSignature(client)) {
|
||||
builder.signatureAlgorithm(getSignatureAlgorithm(client))
|
||||
.signWith(realm.getPrivateKey(), realm.getPublicKey())
|
||||
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||
.signAssertions();
|
||||
}
|
||||
if (!includeAuthnStatement(client)) {
|
||||
|
@ -218,9 +237,13 @@ public class SamlProtocol implements LoginProtocol {
|
|||
builder.attribute(X500SAMLProfileConstants.GIVEN_NAME.getFriendlyName(), user.getFirstName());
|
||||
builder.attribute(X500SAMLProfileConstants.SURNAME.getFriendlyName(), user.getLastName());
|
||||
}
|
||||
if (ClaimMask.hasUsername(model.getAllowedClaimsMask())) {
|
||||
builder.attribute(X500SAMLProfileConstants.USERID.getFriendlyName(), user.getUsername());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Response consentDenied(ClientSessionModel clientSession) {
|
||||
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
||||
|
@ -238,7 +261,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
.destination(client.getClientId());
|
||||
if (requiresRealmSignature(client)) {
|
||||
logoutBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
|
||||
.signWith(realm.getPrivateKey(), realm.getPublicKey())
|
||||
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||
.signDocument();
|
||||
}
|
||||
/*
|
||||
|
|
|
@ -24,10 +24,12 @@ import org.keycloak.services.resources.RealmsResource;
|
|||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.util.StreamUtil;
|
||||
import org.picketlink.common.constants.GeneralConstants;
|
||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||
import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.picketlink.identity.federation.saml.v2.SAML2Object;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.NameIDPolicyType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.RequestAbstractType;
|
||||
import org.picketlink.identity.federation.web.util.RedirectBindingUtil;
|
||||
|
||||
|
@ -207,6 +209,21 @@ public class SamlService {
|
|||
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
||||
clientSession.setNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID());
|
||||
|
||||
// Handle NameIDPolicy from SP
|
||||
NameIDPolicyType nameIdPolicy = requestAbstractType.getNameIDPolicy();
|
||||
if(nameIdPolicy != null) {
|
||||
String nameIdFormat = nameIdPolicy.getFormat().toString();
|
||||
// TODO: Handle AllowCreate too, relevant for persistent NameID.
|
||||
if(isSupportedNameIdFormat(nameIdFormat)) {
|
||||
clientSession.setNote(GeneralConstants.NAMEID_FORMAT, nameIdFormat);
|
||||
} else {
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unsupported NameIDFormat.");
|
||||
}
|
||||
} else {
|
||||
clientSession.setNote(GeneralConstants.NAMEID_FORMAT, JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get());
|
||||
}
|
||||
|
||||
Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
|
||||
if (response != null) return response;
|
||||
|
||||
|
@ -226,6 +243,15 @@ public class SamlService {
|
|||
return forms.createLogin();
|
||||
}
|
||||
|
||||
private boolean isSupportedNameIdFormat(String nameIdFormat) {
|
||||
if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get()) ||
|
||||
nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get()) ||
|
||||
nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract String getBindingType();
|
||||
|
||||
protected Response logoutRequest(LogoutRequestType requestAbstractType, ClientModel client) {
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
<EntityDescriptor entityID="${idp.entityID}">
|
||||
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
||||
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient
|
||||
</NameIDFormat>
|
||||
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
|
||||
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
|
||||
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
|
||||
|
||||
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||
Location="${idp.sso.HTTP-POST}" />
|
||||
<SingleSignOnService
|
||||
|
|
Loading…
Reference in a new issue