Permalink
Browse files

CFID-96: remove private profile and initialise empty database with ad…

…min user

Change-Id: Idd30232f01733acc0d77be93b116fe840b32f655
  • Loading branch information...
1 parent 4e2118b commit ff268dec3bd81336559474df8112eba0ba5fd6a1 @dsyer dsyer committed Jan 18, 2012
View
@@ -1,5 +1,7 @@
target/
*~
+#*
+*#
*/src/main/*/META-INF/
*/src/main/*/WEB-INF/lib/
.access_token
View
@@ -123,39 +123,75 @@ Security OAuth that can do the heavy lifting if your client is Java.
By default `uaa` will launch with a context root `/uaa`. There is a
Maven profile `vcap` to launch with context root `/`.
+### Configuration
+
+There is a `uaa.yml` in the application which provides defaults to the
+placeholders in the Spring XML. Wherever you see
+`${placeholder.name}` in the XML there is an opportunity to override
+it either by providing a System property (`-D` to JVM) with the same
+name, or an environment-specific `uaa.yml` under
+`env['CLOUD_FOUNDRY_CONFIG_PATH']/uaa.yml`. When vcap is deployed the
+`CLOUD_FOUNDRY_CONFIG_PATH` is defined according to the way it was
+installed.
+
+All passwords and client secrets in the config files must be encypted
+using BCrypt. In Java you can do it like this (with
+`spring-securty-crypto` on the classpath):
+
+ String password = BCrypt.hashpw("plaintext");
+
+In ruby you can do it like this:
+
+ require 'bcrypt'
+ password = BCrypt::Password.create('plaintext')
+
### User Account Data
The default is to use an in-memory, hash-based user store that is
pre-populated with some test users: e.g. `dale` has password
`password` and `marissa` has password `koala`.
-To use a RDBMS for user data activate the Spring profiles `jdbc` and
+To use a RDBMS for user data, activate the Spring profiles `jdbc` and
one of `hsqldb` or `postgresql`. The opposite is `!jdbc` which needs
to be specified explicitly if any other profiles are active. The
`hsqldb` profile will start up with an in-memory RDBMS by default.
-Warning: the database will start empty, so no users can log in until
-the first account is created.
The active profiles can be configured by passing the
`spring.profiles.active` parameter to the JVM. For, example to run
with an embedded HSQL database:
- mvn -Dspring.profiles.active=jdbc,hsqldb,!private,!legacy tomcat:run
+ mvn -Dspring.profiles.active=jdbc,hsqldb,!legacy tomcat:run
Or to use PostgreSQL instead of HSQL:
- mvn -Dspring.profiles.active=jdbc,postgresql,!private,!legacy tomcat:run
+ mvn -Dspring.profiles.active=jdbc,postgresql,!legacy tomcat:run
-To bootstrap a microcloud type environment you need the SCIM user
-endpoints to be unsecure so that a user can create an account and set
-its password to bootstrap the system. For this use the Spring profile
-`private`. The opposite is `!private` which needs to be specified
-explicitly if any other profiles are active.
-
-To launch in legacy mode with the CF.com cloud controller as the
-authentication and token source use profile `legacy`. The opposite is
-`!legacy` which needs to be specified explicitly if any other profiles
-are active.
+To bootstrap a microcloud type environment you need an admin user.
+For this there is a database initializer component that inserts an
+admin user if it finds an empty database on startup. Override the
+default settings (username/password=admin/admin) in `uaa.yml`:
+
+ bootstrap:
+ admin:
+ username: foo
+ password: $2a$10$yHj...
+ email: admin@test.com
+ family_name: Piper
+ given_name: Peter
+
+(the password has to be bcrypted).
+
+### Legacy Mode
+
+There is a legacy mode where the CF.com cloud controller is used for
+the authentication and token generation. To use this, launch the app
+with Spring profile `legacy` (a Maven profile with the same name is
+provided for convenience as well). The opposite is `!legacy` which
+needs to be specified explicitly if any other profiles are active.
+The cloud controller login URL defaults to
+`http://api.cloudfoundry.com/users/{username}/tokens` - to override it
+provide a System property or `uaa.yml` entry for
+`cloud.controller.login_url`.
## The API Application
View
@@ -67,7 +67,7 @@
<!-- Use this profile in conjunction with 'integration' profile to run tests against a legacy mode server -->
<id>legacy</id>
<properties>
- <spring.profiles.active>!jdbc,!private,legacy</spring.profiles.active>
+ <spring.profiles.active>!jdbc,legacy</spring.profiles.active>
</properties>
<build>
<pluginManagement>
@@ -0,0 +1,153 @@
+package org.cloudfoundry.identity.uaa.user;
+
+import java.util.UUID;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.SmartLifecycle;
+import org.springframework.jdbc.core.JdbcOperations;
+
+/**
+ * Utility to insert an admin user in the database if it is empty. The {@link #start()} method executes on the
+ * {@link SmartLifecycle} callback from Spring if included as a bean definition in an application context. Any other
+ * components that rely on an admin user being present should wait until after this component has started (e.g. by
+ * specifying a later startup phase),
+ *
+ * @author Dave Syer
+ *
+ */
+public class JdbcUaaAdminUserBootstrap implements SmartLifecycle {
+
+ private static final Log logger = LogFactory.getLog(JdbcUaaAdminUserBootstrap.class);
+
+ public static final String USER_FIELDS = "id,username,password,email,authority,givenName,familyName";
+
+ public static final String INSERT_ADMIN_USER_QUERY = "insert into users (" + USER_FIELDS
+ + ") values (?, ?, ?, ?, 1, ?, ?)";
+
+ public static final String COUNT_USER_QUERY = "select count(id) from users";
+
+ private final JdbcOperations jdbcTemplate;
+
+ private String id = UUID.randomUUID().toString();
+
+ private String username = "admin@localhost";
+
+ private String password = "$2a$10$yHj1jr2NYpGC3wu/BTeFDOnD4Jz3K6ALd6XghGXPTCU4WMxKZuRHu";
+
+ private String email;
+
+ private String givenName = "Admin";
+
+ private String familyName = "User";
+
+ private boolean running = false;
+
+ private boolean autoStartup = true;
+
+ private int phase = 0;
+
+ public JdbcUaaAdminUserBootstrap(JdbcOperations jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ /**
+ * Flag to indicate that we should run on startup (default true).
+ *
+ * @param autoStartup the auto startup flag to set
+ */
+ public void setAutoStartup(boolean autoStartup) {
+ this.autoStartup = autoStartup;
+ }
+
+ /**
+ * The phase of autostartup to join if auto startup is true. Default 0.
+ *
+ * @param phase the phase to set
+ */
+ public void setPhase(int phase) {
+ this.phase = phase;
+ }
+
+ /**
+ * The username for the admin user if created (default "admin@localhost").
+ *
+ * @param username the username to set
+ */
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ /**
+ * The password for the admin user if created (default "admin").
+ *
+ * @param password the password to set
+ */
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ /**
+ * The email for the admin user if created (default same as username or <code>username@vcap.me</code>).
+ *
+ * @param email the email to set
+ */
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ /**
+ * The given (first) name for the admin user if created (default "Admin").
+ *
+ * @param givenName the givenName to set
+ */
+ public void setGivenName(String givenName) {
+ this.givenName = givenName;
+ }
+
+ /**
+ * The family (second) name for the admin user if created (default "User").
+ *
+ * @param familyName the familyName to set
+ */
+ public void setFamilyName(String familyName) {
+ this.familyName = familyName;
+ }
+
+ @Override
+ public void start() {
+ running = true;
+ email = email == null ? (username.contains("@") ? username : username + "@vcap.me") : email;
+ int count = jdbcTemplate.queryForInt(COUNT_USER_QUERY);
+ if (count == 0) {
+ logger.info(String.format("Inserting admin user with username=%s, id=%s", id, username));
+ jdbcTemplate.update(INSERT_ADMIN_USER_QUERY, id, username, password, email, givenName, familyName);
+ }
+ }
+
+ @Override
+ public void stop() {
+ }
+
+ @Override
+ public boolean isRunning() {
+ return running;
+ }
+
+ @Override
+ public int getPhase() {
+ return phase;
+ }
+
+ @Override
+ public boolean isAutoStartup() {
+ return autoStartup;
+ }
+
+ @Override
+ public void stop(Runnable callback) {
+ running = false;
+ callback.run();
+ }
+
+}
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns="http://www.springframework.org/schema/beans"
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
@@ -25,30 +24,17 @@
</property>
</bean>
- <beans profile="default,!private">
-
- <http request-matcher-ref="userEndPointRequestMatcher" create-session="stateless" authentication-manager-ref="emptyAuthenticationManager"
- entry-point-ref="http403" access-decision-manager-ref="accessDecisionManager" xmlns="http://www.springframework.org/schema/security">
- <intercept-url pattern="/User/*/password" access="ROLE_CLIENT,SCOPE_PASSWORD" />
- <intercept-url pattern="/Users*" access="ROLE_CLIENT,SCOPE_READ" method="GET" />
- <intercept-url pattern="/User/**" access="ROLE_CLIENT,SCOPE_READ" method="GET" />
- <intercept-url pattern="/User/**" access="ROLE_CLIENT,SCOPE_WRITE" method="DELETE" />
- <intercept-url pattern="/User*" access="ROLE_CLIENT,SCOPE_WRITE" method="POST" />
- <intercept-url pattern="/User/**" access="ROLE_CLIENT,SCOPE_WRITE" method="PUT" />
- <intercept-url pattern="/User/**" access="ROLE_CLIENT,SCOPE_READ" method="GET" />
- <anonymous />
- <custom-filter ref="oauth2ResourceServerFilter" after="EXCEPTION_TRANSLATION_FILTER" />
- </http>
-
- </beans>
-
- <beans profile="private">
-
- <!-- Use this profile for a private deployment like a micro cloud. The SCIM endpoints are deliberately not secure, so you
- can add users and change passwords to bootstrap the system -->
- <http request-matcher-ref="userEndPointRequestMatcher" create-session="stateless" authentication-manager-ref="emptyAuthenticationManager"
- entry-point-ref="http403" xmlns="http://www.springframework.org/schema/security" security="none" />
-
- </beans>
+ <http request-matcher-ref="userEndPointRequestMatcher" create-session="stateless" authentication-manager-ref="emptyAuthenticationManager"
+ entry-point-ref="http403" access-decision-manager-ref="accessDecisionManager" xmlns="http://www.springframework.org/schema/security">
+ <intercept-url pattern="/User/*/password" access="ROLE_CLIENT,SCOPE_PASSWORD" />
+ <intercept-url pattern="/Users*" access="ROLE_CLIENT,SCOPE_READ" method="GET" />
+ <intercept-url pattern="/User/**" access="ROLE_CLIENT,SCOPE_READ" method="GET" />
+ <intercept-url pattern="/User/**" access="ROLE_CLIENT,SCOPE_WRITE" method="DELETE" />
+ <intercept-url pattern="/User*" access="ROLE_CLIENT,SCOPE_WRITE" method="POST" />
+ <intercept-url pattern="/User/**" access="ROLE_CLIENT,SCOPE_WRITE" method="PUT" />
+ <intercept-url pattern="/User/**" access="ROLE_CLIENT,SCOPE_READ" method="GET" />
+ <anonymous />
+ <custom-filter ref="oauth2ResourceServerFilter" after="EXCEPTION_TRANSLATION_FILTER" />
+ </http>
</beans>
@@ -268,6 +268,15 @@
<constructor-arg ref="jdbcTemplate" />
</bean>
+ <bean id="userBootstrap" class="org.cloudfoundry.identity.uaa.user.JdbcUaaAdminUserBootstrap">
+ <constructor-arg ref="jdbcTemplate" />
+ <property name="username" value="${bootstrap.admin.username:admin}"/>
+ <property name="password" value="${bootstrap.admin.password:$2a$10$yHj1jr2NYpGC3wu/BTeFDOnD4Jz3K6ALd6XghGXPTCU4WMxKZuRHu}"/>
+ <property name="email" value="${bootstrap.admin.email:admin@localhost}"/>
+ <property name="familyName" value="${bootstrap.admin.family_name:User}"/>
+ <property name="givenName" value="${bootstrap.admin.given_name:Admin}"/>
+ </bean>
+
<bean id="scimUserProvisioning" class="org.cloudfoundry.identity.uaa.scim.JdbcScimUserProvisioning">
<constructor-arg ref="jdbcTemplate" />
</bean>
@@ -300,7 +309,7 @@
<bean id="userDatabase" class="org.cloudfoundry.identity.uaa.user.LegacyUaaUserDatabase" />
<bean id="authzAuthenticationMgr" class="org.cloudfoundry.identity.uaa.authentication.manager.LegacyAuthenticationManager">
- <property name="cloudControllerUrl" value="${cloud.controller.url:http://api.cloudfoundry.com/users/{username}/tokens}" />
+ <property name="cloudControllerUrl" value="${cloud_controller.login_url:http://api.cloudfoundry.com/users/{username}/tokens}" />
</bean>
<bean id="tokenServices" class="org.cloudfoundry.identity.uaa.oauth.LegacyTokenServices">
@@ -48,7 +48,7 @@ public void cleanup() {
@Test
public void testRootContextWithJdbcUsers() throws Exception {
- System.setProperty("spring.profiles.active", "jdbc,hsqldb,private,!legacy");
+ System.setProperty("spring.profiles.active", "jdbc,hsqldb,!legacy");
context = new GenericXmlApplicationContext(new FileSystemResource("src/main/webapp/WEB-INF/spring-servlet.xml"));
assertNotNull(context.getBean("userDatabase", JdbcUaaUserDatabase.class));
}
@@ -61,7 +61,7 @@ public void testRootContextWithDevUsers() throws Exception {
@Test
public void testRootContextWithJdbcSecureUsers() throws Exception {
- System.setProperty("spring.profiles.active", "jdbc,hsqldb,!private,!legacy");
+ System.setProperty("spring.profiles.active", "jdbc,hsqldb,!legacy");
context = new GenericXmlApplicationContext(new FileSystemResource("src/main/webapp/WEB-INF/spring-servlet.xml"));
assertNotNull(context.getBean("userDatabase", JdbcUaaUserDatabase.class));
FilterChainProxy filterChain = context.getBean(FilterChainProxy.class);
@@ -73,21 +73,10 @@ public void testRootContextWithJdbcSecureUsers() throws Exception {
assertEquals(403, response.getStatus());
}
- @Test
- public void testRootContextWithJdbcUnsecureUsers() throws Exception {
- System.setProperty("spring.profiles.active", "jdbc,hsqldb,private,!legacy");
- context = new GenericXmlApplicationContext(new FileSystemResource("src/main/webapp/WEB-INF/spring-servlet.xml"));
- assertNotNull(context.getBean("userDatabase", JdbcUaaUserDatabase.class));
- FilterChainProxy filterChain = context.getBean(FilterChainProxy.class);
- MockHttpServletResponse response = new MockHttpServletResponse();
- filterChain.doFilter(new MockHttpServletRequest("GET", "/Users"), response, new MockFilterChain());
- assertEquals(200, response.getStatus());
- }
-
@Test
public void testOverrideYmlConfig() throws Exception {
System.setProperty("CLOUD_FOUNDRY_CONFIG_PATH", "src/test/resources/test/config");
- System.setProperty("spring.profiles.active", "jdbc,hsqldb,!private,legacy");
+ System.setProperty("spring.profiles.active", "jdbc,hsqldb,legacy");
context = new GenericXmlApplicationContext(new FileSystemResource("src/main/webapp/WEB-INF/spring-servlet.xml"));
Properties properties = context.getBean("applicationProperties", Properties.class);
assertEquals("bar", properties.get("foo"));
Oops, something went wrong.

0 comments on commit ff268de

Please sign in to comment.