diff --git a/src/main/java/uk/ac/ox/it/ords/security/model/DatabaseServer.java b/src/main/java/uk/ac/ox/it/ords/security/model/DatabaseServer.java new file mode 100644 index 0000000..4e54b6c --- /dev/null +++ b/src/main/java/uk/ac/ox/it/ords/security/model/DatabaseServer.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 University of Oxford + * + * 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 uk.ac.ox.it.ords.security.model; + +/** + * Configuration and credentials for a database server + */ +public class DatabaseServer { + + private String host; + private int port; + private String username; + private String password; + + /** + * The database to connect to when no specific database is indicated; for on-premise this is typically "postgres" + * but for cloud database servers is either a random GUID or the username + */ + private String masterDatabaseName; + + public String getHost() { + return host; + } + public void setHost(String host) { + this.host = host; + } + public int getPort() { + return port; + } + public void setPort(int port) { + this.port = port; + } + public String getUsername() { + return username; + } + public void setUsername(String username) { + this.username = username; + } + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } + public String getMasterDatabaseName() { + return masterDatabaseName; + } + public void setMasterDatabaseName(String masterDatabaseName) { + this.masterDatabaseName = masterDatabaseName; + } + + public String getUrl(){ + return "jdbc:postgresql://" + this.getHost() + ":" + this.getPort() + "/" + this.getMasterDatabaseName(); + } + + public String getUrl(String database){ + return "jdbc:postgresql://" + this.getHost() + ":" + this.getPort() + "/" + database; + } + +} diff --git a/src/main/java/uk/ac/ox/it/ords/security/services/ServerConfigurationService.java b/src/main/java/uk/ac/ox/it/ords/security/services/ServerConfigurationService.java new file mode 100644 index 0000000..1ad2203 --- /dev/null +++ b/src/main/java/uk/ac/ox/it/ords/security/services/ServerConfigurationService.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015 University of Oxford + * + * 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 uk.ac.ox.it.ords.security.services; + +import java.util.List; +import java.util.ServiceLoader; + +import uk.ac.ox.it.ords.security.model.DatabaseServer; +import uk.ac.ox.it.ords.security.services.impl.ServerConfigurationServiceImpl; + +/** + * Interface for obtaining credentials for database servers. The default implementation + * stores credentials in a configuration file on the server file system, but other + * implementations could use a database table, registry, or external API + */ +public interface ServerConfigurationService { + + /** + * Gets the specified database server + * @return a DatabaseServer instance + * @throws Exception if the database configuration file is corrupt + */ + public DatabaseServer getDatabaseServer(String host) throws Exception; + + /** + * Gets the preferred database server to use for user data + * @return a DatabaseServer instance + * @throws Exception if the database configuration file is corrupt + */ + public DatabaseServer getDatabaseServer() throws Exception; + + /** + * Gets the preferred database server to use for internal metadata + * @return a DatabaseServer instance + * @throws Exception if the database configuration file is corrupt + */ + public DatabaseServer getOrdsDatabaseServer() throws Exception; + + /** + * Gets all configured database servers + * @return a List of DatabaseServers + * @throws Exception if the database configuration file is corrupt + */ + public List getAllDatabaseServers() throws Exception; + + /** + * Factory for obtaining implementations + */ + public static class Factory { + private static ServerConfigurationService provider; + public static ServerConfigurationService getInstance() { + // + // Use the service loader to load an implementation if one is available + // Place a file called uk.ac.ox.oucs.ords.utilities.csv in src/main/resources/META-INF/services + // containing the classname to load as the CsvService implementation. + // By default we load the Hibernate implementation. + // + if (provider == null){ + ServiceLoader ldr = ServiceLoader.load(ServerConfigurationService.class); + for (ServerConfigurationService service : ldr) { + // We are only expecting one + provider = service; + } + } + // + // If no service provider is found, use the default + // + if (provider == null){ + provider = ServerConfigurationServiceImpl.getInstance(); + } + + return provider; + } + } + + +} diff --git a/src/main/java/uk/ac/ox/it/ords/security/services/impl/ServerConfigurationServiceImpl.java b/src/main/java/uk/ac/ox/it/ords/security/services/impl/ServerConfigurationServiceImpl.java new file mode 100644 index 0000000..5c5116e --- /dev/null +++ b/src/main/java/uk/ac/ox/it/ords/security/services/impl/ServerConfigurationServiceImpl.java @@ -0,0 +1,168 @@ +/* + * Copyright 2015 University of Oxford + * + * 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 uk.ac.ox.it.ords.security.services.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.configuration.XMLConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import uk.ac.ox.it.ords.security.configuration.MetaConfiguration; +import uk.ac.ox.it.ords.security.model.DatabaseServer; +import uk.ac.ox.it.ords.security.services.ServerConfigurationService; + +/** + * Default implementation of the database credentials interface that stores + * credentials in a file on the server. + */ +public class ServerConfigurationServiceImpl implements ServerConfigurationService { + + public static Logger log = LoggerFactory.getLogger(ServerConfigurationServiceImpl.class); + + /** + * The default configuration location; this can be overridden in config.xml to point + * to another file. + */ + public static final String DEFAULT_SERVER_CONFIG_LOCATION = "databaseservers.xml"; + + /** + * Singleton instance + */ + private static ServerConfigurationServiceImpl instance; + + /** + * Private constructor for singleton + */ + private ServerConfigurationServiceImpl(){ + try { + load(); + } catch (Exception e) { + log.error("Couldn't load database server credentials"); + } + } + + /** + * Get the service instance + * @return the service instance + */ + public static ServerConfigurationServiceImpl getInstance(){ + if (instance == null) instance = new ServerConfigurationServiceImpl(); + + return instance; + } + + /** + * The servers defined in the configuration file + */ + private List servers; + + /** + * The server for the internal metadata + */ + private DatabaseServer metadataServer; + + /** + * Loads the configuration file + * @return + * @throws Exception + */ + protected void load() throws Exception { + + String serverConfigurationLocation = DEFAULT_SERVER_CONFIG_LOCATION; + + // + // Check if there is a different load location from the default + // + try { + serverConfigurationLocation = MetaConfiguration.getConfiguration().getString("ords.server.configuration"); + if (serverConfigurationLocation == null){ + log.debug("No server configuration location set; using defaults"); + serverConfigurationLocation = DEFAULT_SERVER_CONFIG_LOCATION; + } + } catch (Exception e) { + log.debug("No server configuration location set; using defaults"); + serverConfigurationLocation = DEFAULT_SERVER_CONFIG_LOCATION; + } + + // + // Load the Server Configuration file + // + XMLConfiguration xmlServerConfiguration = new XMLConfiguration(); + try { + xmlServerConfiguration.setFileName(serverConfigurationLocation); + xmlServerConfiguration.load(); + } catch (Exception e1) { + log.error("Cannot read server configuration at " + serverConfigurationLocation); + throw new Exception("Cannot read server configuration"); + } + + // + // Read the server list + // + int serverArray = xmlServerConfiguration.getStringArray("server[@host]").length; + + servers = new ArrayList(); + + for (int i = 0; i < serverArray; i++){ + DatabaseServer databaseServer = new DatabaseServer(); + + databaseServer.setHost(xmlServerConfiguration.getString("server("+i+")[@host]")); + databaseServer.setPort(xmlServerConfiguration.getInt("server("+i+")[@port]")); + databaseServer.setUsername(xmlServerConfiguration.getString("server("+i+")[@username]")); + databaseServer.setPassword(xmlServerConfiguration.getString("server("+i+")[@password]")); + databaseServer.setMasterDatabaseName(xmlServerConfiguration.getString("server("+i+")[@database]")); + + servers.add(databaseServer); + } + + metadataServer = new DatabaseServer(); + metadataServer.setHost(xmlServerConfiguration.getString("metadata(0)[@host]")); + metadataServer.setPort(xmlServerConfiguration.getInt("metadata(0)[@port]")); + metadataServer.setUsername(xmlServerConfiguration.getString("metadata(0)[@username]")); + metadataServer.setPassword(xmlServerConfiguration.getString("metadata(0)[@password]")); + metadataServer.setMasterDatabaseName(xmlServerConfiguration.getString("metadata(0)[@database]")); + + } + + @Override + public DatabaseServer getDatabaseServer() throws Exception { + return servers.get(0); + } + + @Override + public List getAllDatabaseServers() throws Exception { + return servers; + } + + @Override + public DatabaseServer getDatabaseServer(String host) throws Exception { + for (DatabaseServer databaseServer : servers){ + if (databaseServer.getHost().equalsIgnoreCase(host)){ + return databaseServer; + } + } + return null; + } + + @Override + public DatabaseServer getOrdsDatabaseServer() throws Exception { + return metadataServer; + } + + +} diff --git a/src/main/java/uk/ac/ox/it/ords/security/services/impl/hibernate/HibernateUtils.java b/src/main/java/uk/ac/ox/it/ords/security/services/impl/hibernate/HibernateUtils.java index 1a0956d..e8261e8 100644 --- a/src/main/java/uk/ac/ox/it/ords/security/services/impl/hibernate/HibernateUtils.java +++ b/src/main/java/uk/ac/ox/it/ords/security/services/impl/hibernate/HibernateUtils.java @@ -27,8 +27,10 @@ import uk.ac.ox.it.ords.security.configuration.MetaConfiguration; import uk.ac.ox.it.ords.security.model.Audit; +import uk.ac.ox.it.ords.security.model.DatabaseServer; import uk.ac.ox.it.ords.security.model.Permission; import uk.ac.ox.it.ords.security.model.UserRole; +import uk.ac.ox.it.ords.security.services.ServerConfigurationService; public class HibernateUtils { @@ -54,7 +56,12 @@ protected static void init() { try { - String hibernateConfigLocation = MetaConfiguration.getConfiguration().getString(HIBERNATE_CONFIGURATION_PROPERTY); + String hibernateConfigLocation; + try { + hibernateConfigLocation = MetaConfiguration.getConfiguration().getString(HIBERNATE_CONFIGURATION_PROPERTY); + } catch (Exception e) { + hibernateConfigLocation = null; + } if (hibernateConfigLocation == null){ log.warn("No hibernate configuration found; using default hibernate.cfg.xml"); configuration = new Configuration().configure(); @@ -63,6 +70,14 @@ protected static void init() configuration = new Configuration().configure(new File(hibernateConfigLocation)); } + // + // Add server connection details + // + DatabaseServer databaseServer = ServerConfigurationService.Factory.getInstance().getOrdsDatabaseServer(); + configuration.setProperty("hibernate.connection.url", databaseServer.getUrl()); + configuration.setProperty("hibernate.connection.username", databaseServer.getUsername()); + configuration.setProperty("hibernate.connection.password", databaseServer.getPassword()); + // // Add class mappings. Note we do this programmatically as this is // completely independent of the database configuration. @@ -72,7 +87,7 @@ protected static void init() serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry(); sessionFactory = configuration.buildSessionFactory(serviceRegistry); } - catch (HibernateException he) + catch (Exception he) { throw new ExceptionInInitializerError(he); } diff --git a/src/main/java/uk/ac/ox/it/ords/security/services/impl/hibernate/ODBCServiceImpl.java b/src/main/java/uk/ac/ox/it/ords/security/services/impl/hibernate/ODBCServiceImpl.java index d7489e0..024dd8d 100644 --- a/src/main/java/uk/ac/ox/it/ords/security/services/impl/hibernate/ODBCServiceImpl.java +++ b/src/main/java/uk/ac/ox/it/ords/security/services/impl/hibernate/ODBCServiceImpl.java @@ -28,11 +28,12 @@ import javax.sql.rowset.CachedRowSet; import javax.sql.rowset.RowSetProvider; -import org.apache.commons.configuration.Configuration; import org.apache.log4j.Logger; import uk.ac.ox.it.ords.security.configuration.MetaConfiguration; +import uk.ac.ox.it.ords.security.model.DatabaseServer; import uk.ac.ox.it.ords.security.services.ODBCService; +import uk.ac.ox.it.ords.security.services.ServerConfigurationService; public class ODBCServiceImpl implements ODBCService { @@ -132,11 +133,15 @@ protected void runSQLStatements(List statements, String server, Connection connection = null; Properties connectionProperties = new Properties(); PreparedStatement preparedStatement = null; - Configuration config = MetaConfiguration.getConfiguration(); - connectionProperties.put("user", config.getString(ORDS_DATABASE_USER)); - connectionProperties.put("password", config.getString(ORDS_DATABASE_PASSWORD)); - String connectionURL = "jdbc:postgresql://" + server + "/" - + databaseName; + + DatabaseServer databaseServer = ServerConfigurationService.Factory.getInstance().getOrdsDatabaseServer(); + + connectionProperties.put("user", databaseServer.getUsername()); + connectionProperties.put("password", databaseServer.getPassword()); + + if (databaseName == null) databaseName = databaseServer.getMasterDatabaseName(); + String connectionURL = databaseServer.getUrl(databaseName); + try { connection = DriverManager.getConnection(connectionURL, connectionProperties); @@ -197,14 +202,18 @@ public List getAllODBCRolesForDatabase(String databaseServer, String dat */ protected CachedRowSet runJDBCQuery(String query, List parameters, String server, String databaseName) throws Exception { + Connection connection = null; Properties connectionProperties = new Properties(); PreparedStatement preparedStatement = null; - Configuration config = MetaConfiguration.getConfiguration(); - connectionProperties.put("user", config.getString(ORDS_DATABASE_USER)); - connectionProperties.put("password", config.getString(ORDS_DATABASE_PASSWORD)); - String connectionURL = "jdbc:postgresql://" + server + "/" - + databaseName; + + DatabaseServer databaseServer = ServerConfigurationService.Factory.getInstance().getOrdsDatabaseServer(); + + connectionProperties.put("user", databaseServer.getUsername()); + connectionProperties.put("password", databaseServer.getPassword()); + + if (databaseName == null) databaseName = databaseServer.getMasterDatabaseName(); + String connectionURL = databaseServer.getUrl(databaseName); try { connection = DriverManager.getConnection(connectionURL, connectionProperties); diff --git a/src/main/resources/databaseservers.xml b/src/main/resources/databaseservers.xml new file mode 100644 index 0000000..a25924d --- /dev/null +++ b/src/main/resources/databaseservers.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/hibernate.cfg.xml b/src/main/resources/hibernate.cfg.xml index af655bd..2da6bde 100644 --- a/src/main/resources/hibernate.cfg.xml +++ b/src/main/resources/hibernate.cfg.xml @@ -4,11 +4,8 @@ "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> - jdbc:postgresql://localhost/ordstest org.postgresql.Driver org.hibernate.dialect.PostgreSQLDialect - ords - ords org.hibernate.transaction.JDBCTransactionFactory thread false diff --git a/src/test/java/uk/ac/ox/it/ords/security/services/impl/hibernate/ServerConfigurationServiceTest.java b/src/test/java/uk/ac/ox/it/ords/security/services/impl/hibernate/ServerConfigurationServiceTest.java new file mode 100644 index 0000000..076369c --- /dev/null +++ b/src/test/java/uk/ac/ox/it/ords/security/services/impl/hibernate/ServerConfigurationServiceTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015 University of Oxford + * + * 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 uk.ac.ox.it.ords.security.services.impl.hibernate; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import uk.ac.ox.it.ords.security.model.DatabaseServer; +import uk.ac.ox.it.ords.security.services.ServerConfigurationService; + +public class ServerConfigurationServiceTest { + + @Test + public void loadServers() throws Exception{ + DatabaseServer server = ServerConfigurationService.Factory.getInstance().getDatabaseServer(); + assertEquals("localhost", server.getHost()); + assertEquals(5432, server.getPort()); + assertEquals("ords", server.getUsername()); + assertEquals("ords", server.getPassword()); + assertEquals("ordstest", server.getMasterDatabaseName()); + + assertEquals(1, ServerConfigurationService.Factory.getInstance().getAllDatabaseServers().size()); + assertEquals(server, ServerConfigurationService.Factory.getInstance().getDatabaseServer("localhost")); + } + +} diff --git a/src/test/resources/common.properties b/src/test/resources/common.properties index c0cfaa8..0dbc428 100644 --- a/src/test/resources/common.properties +++ b/src/test/resources/common.properties @@ -6,11 +6,4 @@ # Hibernate config file location if not using the standard # resource loader # -# ords.hibernate.configuration=hibernate.cfg.xml - -# -# These are only needed for testing -# -ords.database.name=ordstest -ords.database.user=ords -ords.database.password=ords \ No newline at end of file +# ords.hibernate.configuration=hibernate.cfg.xml \ No newline at end of file diff --git a/src/test/resources/databaseservers.xml b/src/test/resources/databaseservers.xml new file mode 100644 index 0000000..e882301 --- /dev/null +++ b/src/test/resources/databaseservers.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/src/test/resources/hibernate.cfg.xml b/src/test/resources/hibernate.cfg.xml index 86e186b..8c6fe21 100644 --- a/src/test/resources/hibernate.cfg.xml +++ b/src/test/resources/hibernate.cfg.xml @@ -4,11 +4,8 @@ "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> - jdbc:postgresql://localhost/ordstest org.postgresql.Driver org.hibernate.dialect.PostgreSQLDialect - ords - ords org.hibernate.transaction.JDBCTransactionFactory thread false