Cut out broker features and concentrate on security

This commit is contained in:
Dave Syer
2014-11-22 16:56:43 +00:00
parent 281cb8dd60
commit e47266254a
18 changed files with 225 additions and 943 deletions

View File

@@ -1,6 +1,2 @@
include::intro.adoc[]
== Service Broker Example
include::broker-example.adoc[]

View File

@@ -1,64 +0,0 @@
Example script to deploy and regis#ter a broker:
```
DOMAIN=mydomain.net
cf push app -p target/*.jar --no-start
cf env app | grep SPRING_PROFILES_ACTIVE || cf set-env app SPRING_PROFILES_ACTIVE cloud
cf env app | grep APPLICATION_DOMAIN || cf set-env app APPLICATION_DOMAIN ${DOMAIN}
cf services | grep configserver && cf bind app configserver
cf restart app
cf create-service-broker app user secure http://app.${DOMAIN}
for f in `cf curl /v2/service_plans | grep '\"guid' | sed -e 's/.*: "//' -e 's/".*//'`; do
cf curl v2/service_plans/$f -X PUT -d '{"public":true}'
done
cf create-service app free appi
```
At which point you have a service called "app" and a service instance called "appi":
```
$ cf marketplace
OK
service plans description
app free Singleton service app
$ cf services
Getting services in org default / space development as admin...
OK
name service plan bound apps
appi app free
```
Your application can define a configuration property
`application.domain` (defaults to "cfapps.io") which will be used to
construct the credentials for any app that binds to your service. Or
it can define the URI directly using
`cloudfoundry.service.definition.metadata.uri`.
You can change some other basic metadata by setting config properties:
* `cloudfoundry.service.definition.*` is bound to a
`ServiceDefinition` (defined in spring-boot-cf-service-broker) which
has optional setters for plans and metadata.
* `cloudfoundry.service.broker.*` is bound to an internal bean. It has
optional setters for "name" (the service name), "description" (user
friendly description) and "prefix" (used to create a unique id from
the name).
An app which binds to your service will get credentials that contain a
"uri" property linking to your service. A Spring Boot app can bind to
that through the `vcap.services.[service].credentials.uri` environment
property.
If your service also has a
https://github.com/Netflix/eureka[Eureka core] dependency, and you
can expose it as a Eureka service, then any service which registers
with Eureka will also become a Cloud Foundry service.

View File

@@ -1,3 +1,15 @@
Integration between https://github.com/cloudfoundry[Cloud Foundry]
and https://github.com/spring-cloud[Spring Cloud].
Spring Cloud for Cloudfoundry makes it easy to run
https://github.com/spring-cloud[Spring Cloud] apps in
https://github.com/cloudfoundry[Cloud Foundry] (the Platform as a
Service). Cloud Foundry has the notion of a "service", which is
middlware that you "bind" to an app, essentially providing it with an
environment variable containing credentials (e.g. the location and
username to use for the service).
Add this project as a dependency to any Spring Cloud UI app or REST
service and deploy to Cloudfoundry. If you use Spring Cloud Security
OAuth2 features this will make them bindable to Cloud Foundry services
instead of enironment properties in `oauth2.*`. For a UI app you can
declare `@EnableOAuth2Sso` and bind to a service called "sso", and for
a service you can add `@EnableOAuth2Resource` and bind to a service
called "resource" (see below for how to change the names).

View File

@@ -1,7 +1,50 @@
Add this project to a Spring Boot REST service and deploy to
Cloudfoundry (and use the Actuator for maximum flexibility). It will
expose service-broker endpoints automatically (look in /mappings for
/v2/*) and you can register it with the Cloud Controller as described
here:
http://docs.cloudfoundry.org/services/managing-service-brokers.html[http://docs.cloudfoundry.org/services/managing-service-brokers.html].
Here's a Spring Cloud app with OAuth2 SSO:
.app.groovy
[source,java]
----
@Controller
@EnableOAuth2Sso
class Application {
@RequestMapping('/')
String home() {
'Hello World'
}
}
----
If you run it without any service bindings:
----
$ spring jar app.jar app.groovy
$ cf push -p app.jar
----
it will be secure with (Spring Boot default) Basic authentication,
i.e. the password will be in the logs (or set it with
`security.user.password` as normal). To turn on OAuth2 SSO all you
need to do is bind the app to a service with the right
credentials. For example, a
http://docs.pivotal.io/pivotalcf/devguide/services/user-provided.html[user-provided
service] can be created like this on PWS:
----
$ cf create-user-provided-service sso -p '{clientId:"<my-client>",clientSecret:"<my-secret>",userInfoUri:"https://uaa.run.pivotal.io/userinfo",tokenUri: "https://login.run.pivotal.io/oauth/token",authorizationUri:"https://login.run.pivotal.io/oauth/authorize"}
----
Then bind and restart the app:
----
$ cf bind app sso
$ cf restart app
----
and visit it in a browser. It will redirect to the Cloud Foundry (PWS)
login server instead of challenging for Basic authentication. The
`clientId` and `clientSecret` are credentials of a registered client
in Cloud Foundry. To get a Cloud Foundry client registration for
testing please ask at support@run.pivotal.io (if you want it on PWS),
or your local platform administrator (if it's a private instance).

View File

@@ -2,7 +2,74 @@
include::intro.adoc[]
== Service Broker Example
== Quickstart
include::quickstart.adoc[]
== How Does it Work?
=== OAuth2 Single Sign On
Spring Cloud Security provides the `@EnableOAuth2Sso` annotation and
binds the app to environment properties in `oauth2.\*`. Spring Cloud
for Cloud Foundry just sets up default environment properties so that
it all just works if you bind to a Cloud Foundry service instance
called "sso". The service credentials are mapped to the SSO
properties, i.e. `clientId`, `clientSecret`, `tokenUri`,
`authorizationUri`, `userInfoUri`, `tokenInfoUri1, `jwt.\*` (refer to
the Spring Cloud Security documentation for details of which
combinations will work together). The main thing is that in Cloud
Foundry you only need one service to cover all the necessary
credentials.
To use a different sercice instance name (i.e. not "sso") just set
`oauth2.sso.serviceId` to your custom name.
=== JWT Tokens
Spring Cloud Security already has support for decoding JWT tokens if
you just provide the verification key (as an environment property). In
Cloud Foundry you can pick that property up from a servcice binding
(`jwt.keyValue` or `jwt.keyUri`).
For example the `jwt.keyUri` in PWS is
"https://uaa.run.pivotal.io/token_key":
----
$ curl https://uaa.run.pivotal.io/token_key
{"alg":"SHA256withRSA","value":"-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----\n"}d
----
=== OAuth2 Resource Server
Similarly, the `@EnableOAuth2Resource` annotation will protect your
API endpoints if you bind to a service instance called "resource".
The "sso" service above will work for a resource server as well (so
just bind to that if it's there). If the OAuth2 tokens are JWTs (as in
Cloud Foundry), it is common to use a separate service for resources
to avoid a network round trip decoding the token on every access. A
user-provided-service for an OAuth2 resource can be created like this
on PWS:
----
$ cf create-user-provided-service resource -p '{jwt.keyUri:"https://uaa.run.pivotal.io/token_key"}
----
To use JWT you need to add the verification key as either
`jwt.keyValue` or `jwt.keyUri` (these could be added to the "sso"
service or the "resource" service if you have one).
To use a different sercice instance name (i.e. not "resource" or
"sso") just set `oauth2.resource.serviceId` to your custom name.
=== The Default Environment Keys
The precise mapppings are as follows:
* `oauth2.sso.\*` to `vcap.services.${oauth2.sso.serviceId:sso}.credentials.*`
* `oauth2.client.\*` to `vcap.services.${oauth2.sso.serviceId:sso}.credentials.tokenUri:${vcap.services.${oauth2.resource.serviceId:resource}.credentials.*`
* `oauth2.resource.\*` to `vcap.services.${oauth2.resource.serviceId:resource}.credentials.tokenUri:${vcap.services.${oauth2.sso.serviceId:sso}.credentials.*`
include::broker-example.adoc[]

View File

@@ -1,77 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.cloudfoundry.community.servicebroker.model.Plan;
import org.cloudfoundry.community.servicebroker.model.ServiceDefinition;
import org.springframework.util.DigestUtils;
/**
* @author Dave Syer
*
*/
public class FreeServiceDefinitionFactory {
private final String prefix;
public FreeServiceDefinitionFactory() {
this("");
}
public FreeServiceDefinitionFactory(String prefix) {
this.prefix = prefix;
}
public ServiceDefinition create(String name, String description) {
return new ServiceDefinition(getId(name), name, description, true, getPlans(name));
}
private List<Plan> getPlans(String appName) {
Plan plan = new Plan(getId(appName + "-free"), "free",
"This is a default service plan. All services are created equally.",
getServiceDefinitionMetadata(appName), true);
return new ArrayList<Plan>(Arrays.asList(plan));
}
private Map<String, Object> getServiceDefinitionMetadata(String appName) {
Map<String, Object> sdMetadata = new HashMap<String, Object>();
sdMetadata.put("displayName", appName + "-service");
sdMetadata.put("longDescription", "Platform Service for " + appName);
sdMetadata.put("providerDisplayName", "Pivotal");
sdMetadata.put("documentationUrl", "https://github.com/spring-platform");
sdMetadata.put("supportUrl", "https://github.com/spring-platform");
return sdMetadata;
}
protected String getId(String name) {
try {
String id = DigestUtils.md5DigestAsHex((prefix + name).getBytes("UTF-8"));
return id;
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException();
}
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker;
import org.cloudfoundry.community.servicebroker.model.ServiceInstanceBinding;
/**
* @author Dave Syer
*
*/
public interface ServiceInstanceBindingRepository {
ServiceInstanceBinding findOne(String bindingId);
void delete(String id);
void save(ServiceInstanceBinding binding);
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker;
import java.util.List;
import org.cloudfoundry.community.servicebroker.model.ServiceInstance;
/**
* @author Dave Syer
*
*/
public interface ServiceInstanceRepository {
List<ServiceInstance> findAll();
ServiceInstance findOne(String serviceInstanceId);
void save(ServiceInstance instance);
void delete(String id);
}

View File

@@ -1,161 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker.configuration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.community.servicebroker.model.Catalog;
import org.cloudfoundry.community.servicebroker.model.ServiceDefinition;
import org.cloudfoundry.community.servicebroker.service.BeanCatalogService;
import org.cloudfoundry.community.servicebroker.service.CatalogService;
import org.springframework.cloud.cloudfoundry.broker.FreeServiceDefinitionFactory;
import org.springframework.cloud.netflix.eureka.server.advice.LeaseManagerLite;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.ActionType;
import com.netflix.eureka.lease.Lease;
/**
* @author Dave Syer
*
*/
public class CatalogLeaseManager implements CatalogService, LeaseManagerLite<InstanceInfo>, SmartApplicationListener {
private static Log logger = LogFactory.getLog(CatalogLeaseManager.class);
private Map<String, ServiceDefinition> definitions = new HashMap<String, ServiceDefinition>();
private List<ServiceDefinition> values = new ArrayList<ServiceDefinition>();
public CatalogLeaseManager(InstanceInfo config) {
register(config, 0, false);
}
@Override
public Catalog getCatalog() {
return new Catalog(values);
}
@Override
public ServiceDefinition getServiceDefinition(String serviceId) {
return new BeanCatalogService(getCatalog()).getServiceDefinition(serviceId);
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return EurekaInstanceRegisteredEvent.class.isAssignableFrom(eventType)
|| EurekaInstanceCanceledEvent.class.isAssignableFrom(eventType)
|| EurekaInstanceRenewedEvent.class.isAssignableFrom(eventType);
}
@Override
public int getOrder() {
return 0;
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return true;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof EurekaInstanceRegisteredEvent) {
EurekaInstanceRegisteredEvent registered = (EurekaInstanceRegisteredEvent) event;
register(registered.getInstanceInfo(), registered.isReplication());
}
else if (event instanceof EurekaInstanceCanceledEvent) {
EurekaInstanceCanceledEvent canceled = (EurekaInstanceCanceledEvent) event;
cancel(canceled.getAppName(), canceled.getServerId(), canceled.isReplication());
}
else if (event instanceof EurekaInstanceRenewedEvent) {
EurekaInstanceRenewedEvent renewed = (EurekaInstanceRenewedEvent) event;
renew(renewed.getAppName(), renewed.getServerId(), renewed.isReplication());
}
}
@Override
public void register(InstanceInfo info, boolean isReplication) {
register(info, Lease.DEFAULT_DURATION_IN_SECS, isReplication);
}
@Override
public void register(InstanceInfo info, int leaseDuration, boolean isReplication) {
logger.info("Registration request for: " + info.getAppName());
if (!definitions.containsKey(info.getAppName())) {
logger.info("Registering: " + info.getAppName());
ServiceDefinition definition = getServiceDefinition(info);
definitions.put(info.getAppName(), definition);
values.add(definition);
}
}
@Override
public boolean cancel(String appName, String id, boolean isReplication) {
ServiceDefinition definition = definitions.remove(appName);
if (definition != null) {
logger.info("Cancelling: " + appName);
values.remove(definition);
}
return true;
}
@Override
public boolean renew(String appName, String id, boolean isReplication) {
if (definitions.containsKey(appName)) {
logger.info("Renewing: " + appName);
definitions.get(appName).getMetadata()
.put("timestamp", System.currentTimeMillis());
}
return true;
}
@Override
public void evict() {
Collection<ServiceDefinition> definitions = new ArrayList<ServiceDefinition>(
this.definitions.values());
for (ServiceDefinition definition : definitions) {
InstanceInfo info = (InstanceInfo) definition.getMetadata().get("info");
if (info.getActionType() == ActionType.DELETED) {
cancel(info.getAppName(), info.getId(), false);
}
}
}
private ServiceDefinition getServiceDefinition(InstanceInfo info) {
String name = info.getAppName().toLowerCase();
ServiceDefinition definition = new FreeServiceDefinitionFactory("eureka-")
.create(name, "Eureka-brokered service");
Map<String, Object> map = new HashMap<String, Object>();
map.put("info", (Object) info);
map.put("uri", info.getHomePageUrl());
map.put("timestamp", System.currentTimeMillis());
definition.setMetadata(map);
return definition;
}
}

View File

@@ -1,185 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker.configuration;
import java.util.Arrays;
import java.util.List;
import org.cloudfoundry.community.servicebroker.config.BrokerApiVersionConfig;
import org.cloudfoundry.community.servicebroker.model.Catalog;
import org.cloudfoundry.community.servicebroker.model.ServiceDefinition;
import org.cloudfoundry.community.servicebroker.service.BeanCatalogService;
import org.cloudfoundry.community.servicebroker.service.CatalogService;
import org.cloudfoundry.community.servicebroker.service.ServiceInstanceBindingService;
import org.cloudfoundry.community.servicebroker.service.ServiceInstanceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.cloudfoundry.broker.FreeServiceDefinitionFactory;
import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceBindingRepository;
import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceRepository;
import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanceBindingRepository;
import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanceBindingService;
import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanceRepository;
import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanceService;
import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider;
import com.netflix.eureka.lease.LeaseManager;
/**
* Autoconfiguration providing simple service-broker endpoints and Netflix eureka server
* features. The service-broker endpoints will be enabled if <a
* href="https://github.com/cloudfoundry-community/spring-boot-cf-service-broker"
* >spring-boot-cf-service-broker</a> is on the classpath. By default they just make the
* current app into a service brooker for itself. The Eureka features will be added if <a
* href="https://github.com/Netflix/eureka">ereka-core</a> is on the classpath. When
* active any Eureka services registered using native Netflix APIs will be available as
* Cloudfoundry services. They consist of an interceptor for the {@link LeaseManager} that
* manages the {@link Catalog} of Cloudfoundry servvices.
*
*
* @author Dave Syer
*
*/
@Configuration
@ComponentScan(basePackages = { "org.cloudfoundry.community.servicebroker" }, excludeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = BrokerApiVersionConfig.class),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = BeanCatalogService.class) })
@ConditionalOnClass({ServiceInstanceRepository.class, BrokerApiVersionConfig.class })
@ConditionalOnWebApplication
@AutoConfigureAfter(EurekaClientAutoConfiguration.class)
public class ServiceBrokerAutoConfiguration {
@Configuration
@ConditionalOnMissingBean(EurekaInstanceConfig.class)
protected static class CatalogConfiguration {
@Autowired
private BrokerProperties broker;
@Bean
@ConditionalOnMissingBean(CatalogService.class)
public BeanCatalogService catalogService() {
return new BeanCatalogService(catalog());
}
@Bean
public Catalog catalog() {
return new Catalog(Arrays.asList(serviceDefinition()));
}
@Bean
@ConfigurationProperties("cloudfoundry.service.definition")
public ServiceDefinition serviceDefinition() {
return new FreeServiceDefinitionFactory(broker.getPrefix()).create(
broker.getName(), broker.getDescription());
}
@Component
@ConfigurationProperties("cloudfoundry.server.broker")
public static class BrokerProperties {
private String prefix;
@Value("${spring.application.name:application}")
private String name;
private String description;
public String getPrefix() {
return prefix == null ? name + "-" : prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description == null ? "Singleton service app" : description;
}
public void setDescription(String description) {
this.description = description;
}
}
}
@Configuration
@ConditionalOnBean(EurekaInstanceConfig.class)
protected static class EurekaCatalogConfiguration {
@Bean
public CatalogLeaseManager catalogLeaseManager(EurekaInstanceConfig config) {
InstanceInfo info = new EurekaConfigBasedInstanceInfoProvider(config).get();
return new CatalogLeaseManager(info);
}
}
@Bean
@ConditionalOnMissingBean(ServiceInstanceService.class)
public SimpleServiceInstanceService serviceInstanceService(
ServiceInstanceRepository repository) {
return new SimpleServiceInstanceService(repository);
}
@Bean
@ConditionalOnMissingBean(ServiceInstanceBindingService.class)
public SimpleServiceInstanceBindingService serviceInstanceBindingService(
CatalogService catalog, ServiceInstanceBindingRepository repository) {
return new SimpleServiceInstanceBindingService(catalog, repository);
}
@Bean
@ConditionalOnMissingBean(ServiceInstanceRepository.class)
public SimpleServiceInstanceRepository serviceInstanceRepository(
CatalogService catalog) {
List<ServiceDefinition> definitions = catalog.getCatalog()
.getServiceDefinitions();
ServiceDefinition definition = definitions.iterator().next();
return new SimpleServiceInstanceRepository(definition.getId());
}
@Bean
@ConditionalOnMissingBean(ServiceInstanceBindingRepository.class)
public SimpleServiceInstanceBindingRepository serviceInstanceBindingRepository(
CatalogService catalog) {
List<ServiceDefinition> definitions = catalog.getCatalog()
.getServiceDefinitions();
ServiceDefinition definition = definitions.iterator().next();
return new SimpleServiceInstanceBindingRepository(definition.getId());
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker.simple;
import org.cloudfoundry.community.servicebroker.model.ServiceInstanceBinding;
import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceBindingRepository;
/**
* @author Dave Syer
*
*/
public class SimpleServiceInstanceBindingRepository implements ServiceInstanceBindingRepository {
private String serviceDefinitionId;
public SimpleServiceInstanceBindingRepository(String serviceDefinitionId) {
this.serviceDefinitionId = serviceDefinitionId;
}
@Override
public ServiceInstanceBinding findOne(String id) {
return new ServiceInstanceBinding(id, serviceDefinitionId, null, null, null);
}
@Override
public void save(ServiceInstanceBinding instance) {
}
@Override
public void delete(String id) {
}
}

View File

@@ -1,107 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker.simple;
import java.util.HashMap;
import java.util.Map;
import org.cloudfoundry.community.servicebroker.exception.ServiceBrokerException;
import org.cloudfoundry.community.servicebroker.exception.ServiceInstanceBindingExistsException;
import org.cloudfoundry.community.servicebroker.model.ServiceDefinition;
import org.cloudfoundry.community.servicebroker.model.ServiceInstance;
import org.cloudfoundry.community.servicebroker.model.ServiceInstanceBinding;
import org.cloudfoundry.community.servicebroker.service.CatalogService;
import org.cloudfoundry.community.servicebroker.service.ServiceInstanceBindingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceBindingRepository;
import org.springframework.stereotype.Service;
/**
* Simple impl to bind services.
*
* @author sgreenberg@gopivotal.com
*
*/
@Service
public class SimpleServiceInstanceBindingService implements ServiceInstanceBindingService {
private ServiceInstanceBindingRepository repository;
private CatalogService catalog;
@Value("${application.domain:cfapps.io}")
private String applicationDomain;
@Autowired
public SimpleServiceInstanceBindingService(CatalogService catalog,
ServiceInstanceBindingRepository repository) {
this.catalog = catalog;
this.repository = repository;
}
@Override
public ServiceInstanceBinding createServiceInstanceBinding(String bindingId,
ServiceInstance serviceInstance, String serviceId, String planId,
String appGuid) throws ServiceInstanceBindingExistsException,
ServiceBrokerException {
ServiceInstanceBinding binding = repository.findOne(bindingId);
if (binding != null && binding.getAppGuid()!=null) {
throw new ServiceInstanceBindingExistsException(binding);
}
binding = new ServiceInstanceBinding(bindingId, serviceInstance.getId(),
getCredentials(serviceInstance, serviceId, planId, appGuid), null, appGuid);
repository.save(binding);
return binding;
}
protected Map<String, Object> getCredentials(ServiceInstance instance,
String serviceId, String planId, String appGuid) {
Map<String, Object> credentials = new HashMap<String, Object>();
credentials.put("uri", findUriFromService(serviceId));
credentials.put("domain", applicationDomain);
return credentials;
}
protected String findUriFromService(String serviceId) {
ServiceDefinition definition = catalog.getServiceDefinition(serviceId);
if (definition != null && definition.getId().equals(serviceId)) {
String uri = (String) definition.getMetadata().get("uri");
if (uri != null) {
return uri;
}
return "http://" + definition.getName() + "." + applicationDomain;
}
throw new IllegalStateException("Cannot locate service in catalog: " + serviceId);
}
@Override
public ServiceInstanceBinding getServiceInstanceBinding(String id) {
return repository.findOne(id);
}
@Override
public ServiceInstanceBinding deleteServiceInstanceBinding(String id)
throws ServiceBrokerException {
ServiceInstanceBinding binding = getServiceInstanceBinding(id);
if (binding != null) {
repository.delete(id);
}
return binding;
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker.simple;
import java.util.ArrayList;
import java.util.List;
import org.cloudfoundry.community.servicebroker.model.ServiceInstance;
import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceRepository;
/**
* @author Dave Syer
*
*/
public class SimpleServiceInstanceRepository implements ServiceInstanceRepository {
private String serviceDefinitionId;
public SimpleServiceInstanceRepository(String serviceDefinitionId) {
// TODO: add dashboard URL
this.serviceDefinitionId = serviceDefinitionId;
}
@Override
public List<ServiceInstance> findAll() {
return new ArrayList<ServiceInstance>();
}
@Override
public ServiceInstance findOne(String serviceInstanceId) {
return new ServiceInstance(serviceInstanceId, serviceDefinitionId, null, null, null, null);
}
@Override
public void save(ServiceInstance instance) {
}
@Override
public void delete(String id) {
}
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker.simple;
import java.util.List;
import org.cloudfoundry.community.servicebroker.exception.ServiceBrokerException;
import org.cloudfoundry.community.servicebroker.exception.ServiceInstanceExistsException;
import org.cloudfoundry.community.servicebroker.model.ServiceDefinition;
import org.cloudfoundry.community.servicebroker.model.ServiceInstance;
import org.cloudfoundry.community.servicebroker.service.ServiceInstanceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceRepository;
import org.springframework.stereotype.Service;
@Service
public class SimpleServiceInstanceService implements ServiceInstanceService {
private ServiceInstanceRepository repository;
@Autowired
public SimpleServiceInstanceService(ServiceInstanceRepository repository) {
this.repository = repository;
}
@Override
public List<ServiceInstance> getAllServiceInstances() {
return repository.findAll();
}
@Override
public ServiceInstance createServiceInstance(ServiceDefinition service,
String serviceInstanceId, String planId, String organizationGuid,
String spaceGuid)
throws ServiceInstanceExistsException, ServiceBrokerException {
ServiceInstance instance = repository.findOne(serviceInstanceId);
if (instance != null && instance.getOrganizationGuid()!=null) {
throw new ServiceInstanceExistsException(instance);
}
instance = new ServiceInstance(serviceInstanceId, service.getId(),
planId, organizationGuid, spaceGuid, null);
repository.save(instance);
return instance;
}
@Override
public ServiceInstance getServiceInstance(String id) {
return repository.findOne(id);
}
@Override
public ServiceInstance deleteServiceInstance(String id) throws ServiceBrokerException {
ServiceInstance instance = repository.findOne(id);
repository.delete(id);
return instance;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.environment;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.MapPropertySource;
/**
* @author Dave Syer
*
*/
public class VcapServiceCredentialsListener implements
ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
// After VcapApplicationListener and ConfigFileApplicationListener so values here can
// use those ones
private int order = ConfigFileApplicationListener.DEFAULT_ORDER + 1;
@Override
public int getOrder() {
return order;
}
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
Map<String, Object> source = new HashMap<String, Object>();
source.put("oauth.sso.logoutUri",
"${vcap.services.${oauth2.sso.serviceId:sso}.credentials.logoutUri:}");
source.put("oauth2.resource.id",
"${vcap.services.${oauth2.resource.serviceId:resource}.credentials.id:}");
source.put(
"oauth2.resource.userInfoUri",
"${vcap.services.${oauth2.resource.serviceId:resource}.credentials.userInfoUri:"
+ "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.userInfoUri:}}");
source.put(
"oauth2.resource.tokenInfoUri",
"${vcap.services.${oauth2.resource.serviceId:resource}.credentials.tokenInfoUri:"
+ "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.tokenInfoUri:}}");
source.put(
"oauth2.resource.jwt.keyUri",
"${vcap.services.${oauth2.resource.serviceId:resource}.credentials.keyUri:"
+ "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.keyUri:}}");
source.put(
"oauth2.resource.jwt.keyValue",
"${vcap.services.${oauth2.resource.serviceId:resource}.credentials.keyValue:"
+ "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.keyValue:}}");
source.put(
"oauth2.client.tokenUri",
"${vcap.services.${oauth2.sso.serviceId:sso}.credentials.tokenUri:"
+ "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.tokenUri:}}");
source.put(
"oauth2.client.authorizationUri",
"${vcap.services.${oauth2.sso.serviceId:sso}.credentials.authorizationUri:"
+ "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.authorizationUri:}}");
source.put(
"oauth2.client.clientId",
"${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientId:"
+ "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.clientId:}}");
source.put(
"oauth2.client.clientSecret",
"${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientSecret:"
+ "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.clientSecret:}}");
source.put(
"oauth2.client.scope",
"${vcap.services.${oauth2.sso.serviceId:sso}.credentials.scope:"
+ "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.scope:}}");
event.getEnvironment().getPropertySources()
.addLast(new MapPropertySource("cloudDefaultBindings", source));
}
}

View File

@@ -1,2 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.cloudfoundry.broker.configuration.ServiceBrokerAutoConfiguration
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.cloudfoundry.environment.VcapServiceCredentialsListener

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker.sample;
import java.security.Principal;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Configuration
@EnableEurekaClient
@EnableAutoConfiguration
@RestController
public class Application {
@RequestMapping("/")
public Principal home(Principal principal) {
return principal;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* 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.springframework.cloud.cloudfoundry.broker.sample;
import static org.junit.Assert.assertEquals;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest({ "server.port=0"})
public class ApplicationTests {
@Value("${local.server.port}")
private int port = 0;
@Test
public void catalogLoads() {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + port + "/v2/catalog", Map.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
}
}