Commit 5565d1b2 authored by Gradl, Tobias's avatar Gradl, Tobias
Browse files

713: Port SAML administration dialogs

Task-Url: https://minfba.de.dariah.eu/mantisbt/view.php?id=713
parent b3042b9d
package eu.dariah.de.dariahsp.web.metadata;
import eu.dariah.de.dariahsp.web.controller.SamlMetadataController;
/* Copyright 2011 Vladimir Schafer
*
* 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 eu.dariah.de.dariahsp.saml.web.metadata;
import eu.dariah.de.dariahsp.saml.web.controller.MetadataController;
import eu.dariah.de.dariahsp.saml.web.controller.MetadataController.AllowedSSOBindings;
/**
* Form able to store UI data related to metadata.
*/
public class SamlMetadataForm {
public class MetadataForm {
private boolean store;
private String entityId;
private String securityProfile;
private String sslSecurityProfile;
private String sslHostnameVerification;
private String baseURL;
private String alias;
private boolean signMetadata = true;
private boolean signMetadata;
private String serializedMetadata;
private String configuration;
private String signingAlgorithm;
private String[] nameID;
private String[] ssoBindings = new String[] {
SamlMetadataController.AllowedSSOBindings.SSO_ARTIFACT.toString(),
SamlMetadataController.AllowedSSOBindings.SSO_POST.toString(),
MetadataController.AllowedSSOBindings.SSO_POST.toString(),
MetadataController.AllowedSSOBindings.SSO_ARTIFACT.toString()
};
private String ssoDefaultBinding = SamlMetadataController.AllowedSSOBindings.SSO_ARTIFACT.toString();
private String ssoDefaultBinding = MetadataController.AllowedSSOBindings.SSO_POST.toString();
private String signingKey;
private String encryptionKey;
......@@ -31,11 +47,11 @@ public class SamlMetadataForm {
private boolean local;
private boolean includeSSO = true;
private boolean includeHokSSO = false;
private boolean includeDiscovery = false;
private boolean includeDiscovery = true;
private boolean includeDiscoveryExtension = false;
private String customDiscoveryURL;
private String customDiscoveryResponseURL;
private boolean requestSigned = true;
private boolean wantAssertionSigned;
......@@ -43,7 +59,7 @@ public class SamlMetadataForm {
private boolean requireLogoutResponseSigned;
private boolean requireArtifactResolveSigned;
public SamlMetadataForm() {
public MetadataForm() {
}
public String getEntityId() {
......@@ -198,20 +214,12 @@ public class SamlMetadataForm {
this.includeDiscovery = includeDiscovery;
}
public boolean isIncludeHokSSO() {
return includeHokSSO;
public boolean isIncludeDiscoveryExtension() {
return includeDiscoveryExtension;
}
public void setIncludeHokSSO(boolean includeHokSSO) {
this.includeHokSSO = includeHokSSO;
}
public boolean isIncludeSSO() {
return includeSSO;
}
public void setIncludeSSO(boolean includeSSO) {
this.includeSSO = includeSSO;
public void setIncludeDiscoveryExtension(boolean includeDiscoveryExtension) {
this.includeDiscoveryExtension = includeDiscoveryExtension;
}
public String[] getNameID() {
......@@ -230,6 +238,14 @@ public class SamlMetadataForm {
this.customDiscoveryURL = customDiscoveryURL;
}
public String getCustomDiscoveryResponseURL() {
return customDiscoveryResponseURL;
}
public void setCustomDiscoveryResponseURL(String customDiscoveryResponseURL) {
this.customDiscoveryResponseURL = customDiscoveryResponseURL;
}
public String[] getSsoBindings() {
return ssoBindings;
}
......@@ -246,4 +262,20 @@ public class SamlMetadataForm {
this.ssoDefaultBinding = ssoDefaultBinding;
}
}
\ No newline at end of file
public String getSslHostnameVerification() {
return sslHostnameVerification;
}
public void setSslHostnameVerification(String sslHostnameVerification) {
this.sslHostnameVerification = sslHostnameVerification;
}
public String getSigningAlgorithm() {
return signingAlgorithm;
}
public void setSigningAlgorithm(String signingAlgorithm) {
this.signingAlgorithm = signingAlgorithm;
}
}
package eu.dariah.de.dariahsp.web.metadata;
/* Copyright 2011 Vladimir Schafer
*
* 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 eu.dariah.de.dariahsp.saml.web.metadata;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
......@@ -14,24 +27,23 @@ import java.net.URL;
/**
* Validator for metadata from.
*/
public class SamlMetadataValidator implements Validator {
public class MetadataValidator implements Validator {
MetadataManager manager;
public SamlMetadataValidator(MetadataManager manager) {
public MetadataValidator(MetadataManager manager) {
this.manager = manager;
}
public boolean supports(Class<?> clazz) {
return clazz.equals(SamlMetadataForm.class);
return clazz.equals(MetadataForm.class);
}
public void validate(Object target, Errors errors) {
SamlMetadataForm metadata = (SamlMetadataForm) target;
MetadataForm metadata = (MetadataForm) target;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "entityId", "required", "Entity id must be set.");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "alias", "required", "Alias must be set.");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "baseURL", "required", "Base URL is required.");
if (metadata.getSecurityProfile() == null) {
......@@ -54,6 +66,14 @@ public class SamlMetadataValidator implements Validator {
}
}
if (metadata.isIncludeDiscovery() && metadata.getCustomDiscoveryResponseURL() != null && metadata.getCustomDiscoveryResponseURL().length() > 0) {
try {
new URL(metadata.getCustomDiscoveryResponseURL());
} catch (MalformedURLException e) {
errors.rejectValue("customDiscoveryResponseURL", null, "Value is not a valid URL.");
}
}
// Bindings
if (metadata.getSsoBindings() == null || metadata.getSsoBindings().length == 0) {
errors.rejectValue("ssoBindings", null, "At least one binding must be specified.");
......
package eu.dariah.de.dariahsp.web.controller;
import org.opensaml.Configuration;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallerFactory;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.util.XMLHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.saml.key.JKSKeyManager;
import org.springframework.security.saml.metadata.*;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.w3c.dom.Element;
import eu.dariah.de.dariahsp.web.metadata.SamlMetadataForm;
import eu.dariah.de.dariahsp.web.metadata.SamlMetadataValidator;
import javax.servlet.http.HttpServletRequest;
import java.security.KeyStoreException;
import java.util.*;
/**
* Class allows manipulation of metadata from web UI.
*/
@Controller
@RequestMapping("/auth/saml/admin")
public class SamlMetadataController {
private static Logger logger = LoggerFactory.getLogger(SamlMetadataController.class);
public static enum AllowedSSOBindings {
SSO_POST, SSO_PAOS, SSO_ARTIFACT, HOKSSO_POST, HOKSSO_ARTIFACT
}
@Autowired(required=false)
MetadataManager metadataManager;
@Autowired(required=false)
JKSKeyManager keyManager;
@RequestMapping
public String metadataList(Model model) throws MetadataProviderException {
model.addAttribute("hostedSP", metadataManager.getHostedSPName());
model.addAttribute("spList", metadataManager.getSPEntityNames());
model.addAttribute("idpList", metadataManager.getIDPEntityNames());
model.addAttribute("metadata", metadataManager.getAvailableProviders());
return "auth/saml/metadata/list";
}
@RequestMapping(value = "/refresh")
public String refreshMetadata(Model model) throws MetadataProviderException {
metadataManager.refreshMetadata();
return metadataList(model);
}
@RequestMapping(value = "/provider")
public String displayProvider(@RequestParam("providerIndex") int providerIndex, Model model) {
ExtendedMetadataDelegate delegate = metadataManager.getAvailableProviders().get(providerIndex);
model.addAttribute("provider", delegate);
model.addAttribute("providerIndex", providerIndex);
return "auth/saml/metadata/provider";
}
@RequestMapping(value = "/removeProvider")
public String removeProvider(@RequestParam int providerIndex, Model model) throws MetadataProviderException {
ExtendedMetadataDelegate delegate = metadataManager.getAvailableProviders().get(providerIndex);
metadataManager.removeMetadataProvider(delegate);
return metadataList(model);
}
@RequestMapping(value = "/generate")
public String generateMetadata(HttpServletRequest request, Model model) throws KeyStoreException {
SamlMetadataForm defaultForm = new SamlMetadataForm();
model.addAttribute("availableKeys", getAvailablePrivateKeys());
defaultForm.setBaseURL(getBaseURL(request));
defaultForm.setEntityId(getEntityId(request));
defaultForm.setAlias(getEntityId(request));
defaultForm.setNameID(MetadataGenerator.defaultNameID.toArray(new String[MetadataGenerator.defaultNameID.size()])); // TODO
// array
// vs
// collection
model.addAttribute("metadata", defaultForm);
return "auth/saml/metadata/generate";
}
@RequestMapping(value = "/create")
public String createMetadata(@ModelAttribute("metadata") SamlMetadataForm metadata, BindingResult bindingResult, Model model)
throws MetadataProviderException, MarshallingException, KeyStoreException {
new SamlMetadataValidator(metadataManager).validate(metadata, bindingResult);
if (bindingResult.hasErrors()) {
model.addAttribute("availableKeys", getAvailablePrivateKeys());
return "auth/saml/metadata/generate";
}
MetadataGenerator generator = new MetadataGenerator();
generator.setKeyManager(keyManager);
/*generator.setEntityId(metadata.getEntityId());
generator.setEntityAlias(metadata.getAlias());
generator.setEntityBaseURL(metadata.getBaseURL());
generator.setSignMetadata(metadata.isSignMetadata());
generator.setRequestSigned(metadata.isRequestSigned());
generator.setWantAssertionSigned(metadata.isWantAssertionSigned());
generator.setSigningKey(metadata.getSigningKey());
generator.setEncryptionKey(metadata.getEncryptionKey());
if (metadata.getTlsKey() != null && metadata.getTlsKey().length() > 0) {
generator.setTlsKey(metadata.getTlsKey());
}
*/
Collection<String> bindingsSSO = new LinkedList<String>();
Collection<String> bindingsHoKSSO = new LinkedList<String>();
String defaultBinding = metadata.getSsoDefaultBinding();
int assertionConsumerIndex = 0;
for (String binding : metadata.getSsoBindings()) {
// Set default binding
if (binding.equalsIgnoreCase(defaultBinding)) {
assertionConsumerIndex = bindingsSSO.size() + bindingsHoKSSO.size();
}
// Set included bindings
if (AllowedSSOBindings.SSO_POST.toString().equalsIgnoreCase(binding)) {
bindingsSSO.add(SAMLConstants.SAML2_POST_BINDING_URI);
} else if (AllowedSSOBindings.SSO_ARTIFACT.toString().equalsIgnoreCase(binding)) {
bindingsSSO.add(SAMLConstants.SAML2_ARTIFACT_BINDING_URI);
} else if (AllowedSSOBindings.SSO_PAOS.toString().equalsIgnoreCase(binding)) {
bindingsSSO.add(SAMLConstants.SAML2_PAOS_BINDING_URI);
} else if (AllowedSSOBindings.HOKSSO_POST.toString().equalsIgnoreCase(binding)) {
bindingsHoKSSO.add(SAMLConstants.SAML2_POST_BINDING_URI);
} else if (AllowedSSOBindings.HOKSSO_ARTIFACT.toString().equalsIgnoreCase(binding)) {
bindingsHoKSSO.add(SAMLConstants.SAML2_ARTIFACT_BINDING_URI);
}
}
// Set bindings
generator.setBindingsSSO(bindingsSSO);
generator.setBindingsHoKSSO(bindingsHoKSSO);
generator.setAssertionConsumerIndex(assertionConsumerIndex);
// Discovery
/*if (metadata.isIncludeDiscovery()) {
generator.setIncludeDiscovery(true);
generator.setIncludeDiscoveryExtension(true);
if (metadata.getCustomDiscoveryURL() != null && metadata.getCustomDiscoveryURL().length() > 0) {
generator.setCustomDiscoveryURL(metadata.getCustomDiscoveryURL());
}
} else {
generator.setIncludeDiscovery(false);
generator.setIncludeDiscoveryExtension(false);
}*/
generator.setNameID(Arrays.asList(metadata.getNameID()));
EntityDescriptor descriptor = generator.generateMetadata();
ExtendedMetadata extendedMetadata = generator.generateExtendedMetadata();
extendedMetadata.setSecurityProfile(metadata.getSecurityProfile());
extendedMetadata.setSslSecurityProfile(metadata.getSslSecurityProfile());
extendedMetadata.setRequireLogoutRequestSigned(metadata.isRequireLogoutRequestSigned());
extendedMetadata.setRequireLogoutResponseSigned(metadata.isRequireLogoutResponseSigned());
extendedMetadata.setRequireArtifactResolveSigned(metadata.isRequireArtifactResolveSigned());
if (metadata.isStore()) {
MetadataMemoryProvider memoryProvider = new MetadataMemoryProvider(descriptor);
memoryProvider.initialize();
MetadataProvider metadataProvider = new ExtendedMetadataDelegate(memoryProvider, extendedMetadata);
metadataManager.addMetadataProvider(metadataProvider);
metadataManager.setHostedSPName(descriptor.getEntityID());
metadataManager.setRefreshRequired(true);
metadataManager.refreshMetadata();
}
return displayMetadata(descriptor, extendedMetadata, model);
}
/**
* Displays stored metadata.
*
* @param entityId
* entity ID of metadata to display
* @return model and view
* @throws MetadataProviderException
* in case metadata can't be located
* @throws MarshallingException
* in case de-serialization into string fails
*/
@RequestMapping(value = "/display")
public String displayMetadata(@RequestParam("entityId") String entityId, Model model) throws MetadataProviderException, MarshallingException {
EntityDescriptor entityDescriptor = metadataManager.getEntityDescriptor(entityId);
ExtendedMetadata extendedMetadata = metadataManager.getExtendedMetadata(entityId);
if (entityDescriptor == null) {
throw new MetadataProviderException("Metadata with ID " + entityId + " not found");
}
return displayMetadata(entityDescriptor, extendedMetadata, model);
}
protected String displayMetadata(EntityDescriptor entityDescriptor, ExtendedMetadata extendedMetadata, Model model) throws MarshallingException {
SamlMetadataForm metadata = new SamlMetadataForm();
String fileName = getFileName(entityDescriptor);
metadata.setLocal(extendedMetadata.isLocal());
metadata.setSecurityProfile(extendedMetadata.getSecurityProfile());
metadata.setSslSecurityProfile(extendedMetadata.getSslSecurityProfile());
metadata.setSerializedMetadata(getMetadataAsString(entityDescriptor));
metadata.setConfiguration(getConfiguration(fileName, extendedMetadata));
metadata.setEntityId(entityDescriptor.getEntityID());
metadata.setAlias(extendedMetadata.getAlias());
metadata.setRequireArtifactResolveSigned(extendedMetadata.isRequireArtifactResolveSigned());
metadata.setRequireLogoutRequestSigned(extendedMetadata.isRequireLogoutRequestSigned());
metadata.setRequireLogoutResponseSigned(extendedMetadata.isRequireLogoutResponseSigned());
metadata.setEncryptionKey(extendedMetadata.getEncryptionKey());
metadata.setSigningKey(extendedMetadata.getSigningKey());
metadata.setTlsKey(extendedMetadata.getTlsKey());
// TODO other fields discovery, nameIDs
model.addAttribute("metadata", metadata);
model.addAttribute("storagePath", fileName);
return "auth/saml/metadata/view";
}
protected String getMetadataAsString(EntityDescriptor descriptor) throws MarshallingException {
MarshallerFactory marshallerFactory = Configuration.getMarshallerFactory();
Marshaller marshaller = marshallerFactory.getMarshaller(descriptor);
Element element = marshaller.marshall(descriptor);
return XMLHelper.nodeToString(element);
}
protected String getBaseURL(HttpServletRequest request) {
StringBuffer sb = new StringBuffer();
sb.append(request.getScheme()).append("://").append(request.getServerName()).append(":").append(request.getServerPort());
sb.append(request.getContextPath());
String baseURL = sb.toString();
logger.debug("Base URL {}", baseURL);
return baseURL;
}
protected String getEntityId(HttpServletRequest request) {
logger.debug("Server name used as entity id {}", request.getServerName());
return request.getServerName();
}
protected Map<String, String> getAvailablePrivateKeys() throws KeyStoreException {
Map<String, String> availableKeys = new HashMap<String, String>();
Set<String> aliases = keyManager.getAvailableCredentials();
for (String key : aliases) {
try {
logger.debug("Found key {}", key);
Credential credential = keyManager.getCredential(key);
if (credential.getPrivateKey() != null) {
logger.debug("Adding private key with alias {} and entityID {}", key, credential.getEntityId());
availableKeys.put(key, key + " (" + credential.getEntityId() + ")");
}
} catch (Exception e) {
logger.debug("Error loading key", e);
}
}
return availableKeys;
}
protected String getFileName(EntityDescriptor entityDescriptor) {
StringBuilder fileName = new StringBuilder();
for (Character c : entityDescriptor.getEntityID().toCharArray()) {
if (Character.isJavaIdentifierPart(c)) {
fileName.append(c);
}
}
if (fileName.length() > 0) {
fileName.append("_sp.xml");
return fileName.toString();
} else {
return "default_sp.xml";
}
}
protected String getConfiguration(String fileName, ExtendedMetadata metadata) {
StringBuilder sb = new StringBuilder();
sb.append(
"<bean class=\"org.springframework.security.saml.metadata.ExtendedMetadataDelegate\">\n" + " <constructor-arg>\n"
+ " <bean class=\"org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider\">\n" + " <constructor-arg>\n"
+ " <value type=\"java.io.File\">classpath:security/")
.append(fileName)
.append("</value>\n" + " </constructor-arg>\n" + " <property name=\"parserPool\" ref=\"parserPool\"/>\n"
+ " </bean>\n" + " </constructor-arg>\n" + " <constructor-arg>\n"
+ " <bean class=\"org.springframework.security.saml.metadata.ExtendedMetadata\">\n"
+ " <property name=\"local\" value=\"true\"/>\n" + " <property name=\"alias\" value=\"")
.append(metadata.getAlias()).append("\"/>\n" + " <property name=\"securityProfile\" value=\"").append(metadata.getSecurityProfile())
.append("\"/>\n" + " <property name=\"sslSecurityProfile\" value=\"").append(metadata.getSslSecurityProfile())
.append("\"/>\n" + " <property name=\"signingKey\" value=\"").append(metadata.getSigningKey())
.append("\"/>\n" + " <property name=\"encryptionKey\" value=\"").append(metadata.getEncryptionKey()).append("\"/>\n");
if (metadata.getTlsKey() != null) {
sb.append(" <property name=\"tlsKey\" value=\"").append(metadata.getTlsKey()).append("\"/>\n");
}
sb.append(" <property name=\"requireArtifactResolveSigned\" value=\"").append(metadata.isRequireArtifactResolveSigned())
.append("\"/>\n" + " <property name=\"requireLogoutRequestSigned\" value=\"").append(metadata.isRequireLogoutRequestSigned())
.append("\"/>\n" + " <property name=\"requireLogoutResponseSigned\" value=\"").append(metadata.isRequireLogoutResponseSigned())
.append("\"/>\n");
if (metadata.isIdpDiscoveryEnabled()) {
// TGR: idpDiscoveryEnabled was missing
sb.append(" <property name=\"idpDiscoveryEnabled\" value=\"true\" />")
.append("\n <property name=\"idpDiscoveryURL\" value=\"").append(metadata.getIdpDiscoveryURL())
.append("\"/>\n" + " <property name=\"idpDiscoveryResponseURL\" value=\"").append(metadata.getIdpDiscoveryResponseURL())
.append("\"/>\n");
}
sb.append(" </bean>\n" + " </constructor-arg>\n" + "</bean>");
return sb.toString();
}
}
\ No newline at end of file
......@@ -15,17 +15,21 @@
<!-- Security for the administration UI -->
<security:http pattern="/saml/web/**" use-expressions="false">
<!-- <security:http pattern="/saml/web/**" use-expressions="false">
<security:access-denied-handler error-page="/saml/web/metadata/login"/>
<security:form-login login-processing-url="/saml/web/login" login-page="/saml/web/metadata/login" default-target-url="/saml/web/metadata"/>
<security:intercept-url pattern="/saml/web/metadata/login" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/saml/web/**" access="ROLE_ADMIN"/>
<security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>