Skip to content

Commit

Permalink
Added feature that turns UAA into a SAML Identity Provider.
Browse files Browse the repository at this point in the history
  • Loading branch information
amiri authored and fhanik committed Feb 17, 2016
1 parent fce3af9 commit b93c87a
Show file tree
Hide file tree
Showing 45 changed files with 6,201 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,26 @@
import java.security.cert.X509Certificate;

public class SamlConfig {
private boolean assertionSigned = true;
private boolean requestSigned = true;
private boolean wantAssertionSigned = false;
private boolean wantAuthnRequestSigned = false;
private int assertionTimeToLiveSeconds = 600;
private String certificate;
private String privateKey;
private String privateKeyPassword;

@JsonIgnore
private KeyWithCert keyCert;

public boolean isAssertionSigned() {
return assertionSigned;
}

public void setAssertionSigned(boolean assertionSigned) {
this.assertionSigned = assertionSigned;
}

public boolean isRequestSigned() {
return requestSigned;
}
Expand All @@ -55,6 +66,22 @@ public void setCertificate(String certificate) throws CertificateException {
}
}

public boolean isWantAuthnRequestSigned() {
return wantAuthnRequestSigned;
}

public void setWantAuthnRequestSigned(boolean wantAuthnRequestSigned) {
this.wantAuthnRequestSigned = wantAuthnRequestSigned;
}

public int getAssertionTimeToLiveSeconds() {
return assertionTimeToLiveSeconds;
}

public void setAssertionTimeToLiveSeconds(int assertionTimeToLiveSeconds) {
this.assertionTimeToLiveSeconds = assertionTimeToLiveSeconds;
}

public String getCertificate() {
return certificate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ public enum AuditEventType {
IdentityProviderModifiedEvent(29),
IdentityZoneCreatedEvent(30),
IdentityZoneModifiedEvent(31),
EntityDeletedEvent(32);
EntityDeletedEvent(32),
ServiceProviderCreatedEvent(33),
ServiceProviderModifiedEvent(34);


private final int code;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*******************************************************************************/
package org.cloudfoundry.identity.uaa.provider;

import static org.springframework.http.HttpStatus.OK;
import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.authentication.manager.LdapLoginAuthenticationManager;
import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProvider;
import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderConfigurator;
import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlServiceProviderProvisioning;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.ObjectUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/service-providers")
@RestController
public class SamlServiceProviderEndpoints {

protected static Log logger = LogFactory.getLog(SamlServiceProviderEndpoints.class);

private final SamlServiceProviderProvisioning serviceProviderProvisioning;
private final SamlServiceProviderConfigurator samlConfigurator;

public SamlServiceProviderEndpoints(SamlServiceProviderProvisioning serviceProviderProvisioning,
SamlServiceProviderConfigurator samlConfigurator) {
this.serviceProviderProvisioning = serviceProviderProvisioning;
this.samlConfigurator = samlConfigurator;
}

@RequestMapping(method = POST)
public ResponseEntity<SamlServiceProvider> createServiceProvider(@RequestBody SamlServiceProvider body)
throws MetadataProviderException {
String zoneId = IdentityZoneHolder.get().getId();
body.setIdentityZoneId(zoneId);

SamlServiceProviderDefinition definition = ObjectUtils.castInstance(body.getConfig(),
SamlServiceProviderDefinition.class);
definition.setZoneId(zoneId);
definition.setSpEntityId(body.getEntityId());
samlConfigurator.addSamlServiceProviderDefinition(definition);
body.setConfig(definition);

SamlServiceProvider createdSp = serviceProviderProvisioning.create(body);
return new ResponseEntity<>(createdSp, HttpStatus.CREATED);
}

@RequestMapping(value = "{id}", method = PUT)
public ResponseEntity<SamlServiceProvider> updateServiceProvider(@PathVariable String id,
@RequestBody SamlServiceProvider body) throws MetadataProviderException {
SamlServiceProvider existing = serviceProviderProvisioning.retrieve(id);
String zoneId = IdentityZoneHolder.get().getId();
body.setId(id);
body.setIdentityZoneId(zoneId);
if (!body.configIsValid()) {
return new ResponseEntity<>(UNPROCESSABLE_ENTITY);
}
body.setEntityId(existing.getEntityId());
SamlServiceProviderDefinition definition = ObjectUtils.castInstance(body.getConfig(),
SamlServiceProviderDefinition.class);
definition.setZoneId(zoneId);
definition.setSpEntityId(body.getEntityId());
samlConfigurator.addSamlServiceProviderDefinition(definition);
body.setConfig(definition);

SamlServiceProvider updatedSp = serviceProviderProvisioning.update(body);
return new ResponseEntity<>(updatedSp, OK);
}

@RequestMapping(method = GET)
public ResponseEntity<List<SamlServiceProvider>> retrieveServiceProviders(
@RequestParam(value = "active_only", required = false) String activeOnly) {
Boolean retrieveActiveOnly = Boolean.valueOf(activeOnly);
List<SamlServiceProvider> serviceProviderList = serviceProviderProvisioning.retrieveAll(retrieveActiveOnly,
IdentityZoneHolder.get().getId());
return new ResponseEntity<>(serviceProviderList, OK);
}

@RequestMapping(value = "{id}", method = GET)
public ResponseEntity<SamlServiceProvider> retrieveServiceProvider(@PathVariable String id) {
SamlServiceProvider serviceProvider = serviceProviderProvisioning.retrieve(id);
return new ResponseEntity<>(serviceProvider, OK);
}

@ExceptionHandler(MetadataProviderException.class)
public ResponseEntity<String> handleMetadataProviderException(MetadataProviderException e) {
if (e.getMessage().contains("Duplicate")) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.CONFLICT);
} else {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
}

@ExceptionHandler(JsonUtils.JsonUtilException.class)
public ResponseEntity<String> handleMetadataProviderException() {
return new ResponseEntity<>("Invalid provider configuration.", HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(EmptyResultDataAccessException.class)
public ResponseEntity<String> handleProviderNotFoundException() {
return new ResponseEntity<>("Provider not found.", HttpStatus.NOT_FOUND);
}

protected String getExceptionString(Exception x) {
StringWriter writer = new StringWriter();
x.printStackTrace(new PrintWriter(writer));
return writer.getBuffer().toString();
}

protected static class NoOpLdapLoginAuthenticationManager extends LdapLoginAuthenticationManager {
@Override
public Authentication authenticate(Authentication request) throws AuthenticationException {
return request;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,26 @@

package org.cloudfoundry.identity.uaa.provider.saml;

import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;
import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.provider.IdentityProvider;
import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning;
Expand All @@ -45,18 +58,6 @@
import org.springframework.security.saml.metadata.MetadataManager;
import org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer;

import javax.annotation.PostConstruct;
import javax.xml.namespace.QName;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

public class ZoneAwareMetadataManager extends MetadataManager implements ExtendedMetadataProvider, InitializingBean, DisposableBean, BeanNameAware {

private static final Log logger = LogFactory.getLog(ZoneAwareMetadataManager.class);
Expand All @@ -69,13 +70,11 @@ public class ZoneAwareMetadataManager extends MetadataManager implements Extende
private long lastRefresh = 0;
private Timer timer;
private String beanName = ZoneAwareMetadataManager.class.getName()+"-"+System.identityHashCode(this);
private ProviderChangedListener providerChangedListener;

public ZoneAwareMetadataManager(IdentityProviderProvisioning providerDao,
IdentityZoneProvisioning zoneDao,
SamlIdentityProviderConfigurator configurator,
KeyManager keyManager,
ProviderChangedListener listener) throws MetadataProviderException {
KeyManager keyManager) throws MetadataProviderException {
super(Collections.<MetadataProvider>emptyList());
this.providerDao = providerDao;
this.zoneDao = zoneDao;
Expand All @@ -87,7 +86,6 @@ public ZoneAwareMetadataManager(IdentityProviderProvisioning providerDao,
if (metadataManagers==null) {
metadataManagers = new ConcurrentHashMap<>();
}
providerChangedListener = listener;
}

private class RefreshTask extends TimerTask {
Expand All @@ -114,11 +112,11 @@ public void checkAllProviders() throws MetadataProviderException {
refreshAllProviders();
timer = new Timer("ZoneAwareMetadataManager.Refresh["+beanName+"]", true);
timer.schedule(new RefreshTask(),refreshInterval , refreshInterval);
providerChangedListener.setMetadataManager(this);
}

protected void refreshAllProviders() throws MetadataProviderException {
refreshAllProviders(true);
//refreshAllSamlServiceProviders(true);
}

protected String getThreadNameAndId() {
Expand Down Expand Up @@ -175,7 +173,7 @@ protected void removeSamlProvider(IdentityZone zone, ExtensionMetadataManager ma
}
}

protected ExtensionMetadataManager getManager(IdentityZone zone) {
public ExtensionMetadataManager getManager(IdentityZone zone) {
if (metadataManagers==null) { //called during super constructor
metadataManagers = new ConcurrentHashMap<>();
}
Expand All @@ -191,7 +189,7 @@ protected ExtensionMetadataManager getManager(IdentityZone zone) {
}
return metadataManagers.get(zone);
}
protected ExtensionMetadataManager getManager() {
public ExtensionMetadataManager getManager() {
return getManager(IdentityZoneHolder.get());
}

Expand Down Expand Up @@ -454,6 +452,7 @@ protected Set<ComparableProvider> refreshZoneManager(ExtensionMetadataManager ma

//just so that we can override protected methods
public static class ExtensionMetadataManager extends CachingMetadataManager {

public ExtensionMetadataManager(List<MetadataProvider> providers) throws MetadataProviderException {
super(providers);
//disable internal timers (they only get created when afterPropertiesSet)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.cloudfoundry.identity.uaa.provider.saml.idp;

import org.springframework.security.saml.metadata.ExtendedMetadata;

/**
* A SAML IdP needs information beyond what the standard ExtendedMetadata provides.
* This class exists to provide that extra information.
*/
public class IdpExtendedMetadata extends ExtendedMetadata {

/**
* Generated serialization id.
*/
private static final long serialVersionUID = -7933870052729540864L;

private boolean assertionsSigned = true;
private int assertionTimeToLiveSeconds = 500;

public boolean isAssertionsSigned() {
return assertionsSigned;
}

public void setAssertionsSigned(boolean assertionsSigned) {
this.assertionsSigned = assertionsSigned;
}

public int getAssertionTimeToLiveSeconds() {
return assertionTimeToLiveSeconds;
}

public void setAssertionTimeToLiveSeconds(int assertionTimeToLiveSeconds) {
this.assertionTimeToLiveSeconds = assertionTimeToLiveSeconds;
}

@Override
public IdpExtendedMetadata clone() {
return (IdpExtendedMetadata) super.clone();
}
}
Loading

0 comments on commit b93c87a

Please sign in to comment.