support schema per tenant multi-tenancy. basic auth security filter is extended to detect tenant-id header on request, verify tenant id is valid and then use provided credentials to authenticate user details against users on the tenant specific schema.

This commit is contained in:
Keith Woodlock
2012-07-13 18:05:43 +01:00
parent 816d3decf9
commit 15f0fc4677
34 changed files with 399 additions and 195 deletions
@@ -1,3 +1,7 @@
/* remove column org_id */
ALTER TABLE `mifosngprovider`.`portfolio_client` DROP FOREIGN KEY `FKCE00CAB354DF2770` ;ALTER TABLE `mifosngprovider`.`portfolio_client` DROP COLUMN `org_id` , DROP INDEX `org_id` , ADD INDEX `external_id` (`external_id` ASC) , DROP INDEX `FKCE00CAB354DF2770` ;
ALTER TABLE `portfolio_client` DROP FOREIGN KEY `FKCE00CAB354DF2770`;
ALTER TABLE `portfolio_client`
DROP COLUMN `org_id` ,
DROP INDEX `org_id` ,
ADD INDEX `external_id` (`external_id` ASC) ,
DROP INDEX `FKCE00CAB354DF2770`;
@@ -1,5 +1,5 @@
-- create fund table
DROP TABLE IF EXISTS `mifosngprovider`.`org_fund`;
DROP TABLE IF EXISTS `org_fund`;
CREATE TABLE `org_fund` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`org_id` bigint(20) NOT NULL,
@@ -21,13 +21,13 @@ CREATE TABLE `org_fund` (
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- example of dropping fund column/index/foreign key
-- alter table `mifosngprovider`.`portfolio_product_loan`
-- alter table `portfolio_product_loan`
-- DROP FOREIGN KEY `FKA6A8A7D77240145`,
-- DROP INDEX `FKA6A8A7D77240145`,
-- drop column `fund_id`;
-- associate fund table with loan products making it fund optional at database level
alter table `mifosngprovider`.`portfolio_product_loan`
alter table `portfolio_product_loan`
add column `fund_id` bigint(20) DEFAULT NULL,
add KEY `FKA6A8A7D77240145` (`fund_id`),
add CONSTRAINT `FKA6A8A7D77240145` FOREIGN KEY (`fund_id`) REFERENCES `org_fund` (`id`);
@@ -1,11 +1,11 @@
-- example of dropping fund column/index/foreign key
-- alter table `mifosngprovider`.`portfolio_loan`
-- alter table `portfolio_loan`
-- DROP FOREIGN KEY `FK7C885877240145`,
-- DROP INDEX `FK7C885877240145`,
-- drop column `fund_id`;
-- associate fund table with loans making fund optional at database level
alter table `mifosngprovider`.`portfolio_loan`
alter table `portfolio_loan`
add column `fund_id` bigint(20) DEFAULT NULL,
add KEY `FK7C885877240145` (`fund_id`),
add CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `org_fund` (`id`);
@@ -1,5 +1,5 @@
-- create fund table
DROP TABLE IF EXISTS `mifosngprovider`.`portfolio_office_monetary_transfers`;
DROP TABLE IF EXISTS `portfolio_office_monetary_transfers`;
CREATE TABLE `portfolio_office_monetary_transfers` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`from_office_id` bigint(20) DEFAULT NULL,
@@ -1,6 +1,6 @@
-- create office transaction table
DROP TABLE IF EXISTS `mifosngprovider`.`portfolio_office_monetary_transfers`;
DROP TABLE IF EXISTS `mifosngprovider`.`portfolio_office_transaction`;
DROP TABLE IF EXISTS `portfolio_office_monetary_transfers`;
DROP TABLE IF EXISTS `portfolio_office_transaction`;
CREATE TABLE `portfolio_office_transaction` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`from_office_id` bigint(20) DEFAULT NULL,
+11 -17
View File
@@ -14,19 +14,16 @@ buildscript {
apply plugin: 'war'
apply plugin: 'eclipse'
apply plugin: 'tomcat'
//apply plugin: 'jdepend'
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
springVersion = '3.1.1.RELEASE'
springSecurityOauthVersion = '1.0.0.M6'
springSecurityVersion = '3.1.0.RELEASE'
hibernateValidatorVersion = '4.2.0.Final'
hibernateCoreVersion = '4.1.0.Final'
jerseyVersion = '1.12'
hibernateValidatorVersion = '4.3.0.Final'
hibernateCoreVersion = '4.1.3.Final'
jerseyVersion = '1.13'
springDataJpaVersion = '1.0.3.RELEASE'
springDatamongoDbVersion = '1.0.0.M4'
repositories {
mavenCentral()
@@ -59,7 +56,7 @@ configurations {
}
dependencies {
def tomcatVersion = '7.0.27'
def tomcatVersion = '7.0.29'
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}"
tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") {
@@ -72,17 +69,13 @@ dependencies {
)
compile(
[group: 'javax.servlet', name: 'jstl', version: '1.2'],
[group: 'org.springframework', name: 'spring-core', version: springVersion],
[group: 'org.springframework', name: 'spring-aspects', version: springVersion],
[group: 'org.springframework', name: 'spring-web', version: springVersion],
[group: 'org.springframework', name: 'spring-webmvc', version: springVersion],
[group: 'org.springframework.security', name: 'spring-security-core', version: springSecurityVersion],
[group: 'org.springframework.security', name: 'spring-security-web', version: springSecurityVersion],
[group: 'org.springframework.security', name: 'spring-security-config', version: springSecurityVersion],
[group: 'org.springframework.security', name: 'spring-security-taglibs', version: springSecurityVersion],
[group: 'org.aspectj', name: 'aspectjrt', version: '1.6.11.RELEASE'],
[group: 'org.aspectj', name: 'aspectjrt', version: '1.6.12.RELEASE'],
[group: 'javax.validation', name: 'validation-api', version: '1.0.0.GA'],
[group: 'org.hibernate', name: 'hibernate-validator', version: hibernateValidatorVersion],
@@ -105,13 +98,14 @@ dependencies {
[group: 'org.apache.commons', name: 'commons-email', version: '1.2'],
[group: 'commons-lang', name: 'commons-lang', version: '2.4'],
[group: 'org.slf4j', name: 'slf4j-api', version: '1.6.4'],
[group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.6.4'],
[group: 'org.slf4j', name: 'slf4j-api', version: '1.6.6'],
[group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.6.6'],
[group: 'ch.qos.logback', name: 'logback-core', version: '1.0.1'],
[group: 'ch.qos.logback', name: 'logback-classic', version: '1.0.0'],
[group: 'ch.qos.logback', name: 'logback-core', version: '1.0.6'],
[group: 'ch.qos.logback', name: 'logback-classic', version: '1.0.6'],
[group: 'mysql', name: 'mysql-connector-java', version: '5.1.18'],
[group: 'mysql', name: 'mysql-connector-java', version: '5.1.21'],
[group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: '7.0.29'],
[group: 'pentaho-reporting-engine', name: 'pentaho-reporting-engine-classic-core', version: '3.8.3-GA'],
[group: 'pentaho-reporting-engine', name: 'pentaho-reporting-engine-classic-extensions', version: '3.8.3-GA'],
@@ -27,6 +27,7 @@ import org.mifosng.platform.api.data.ResultsetDataRow;
import org.mifosng.platform.exceptions.AdditionalFieldsNotFoundException;
import org.mifosng.platform.exceptions.PlatformDataIntegrityException;
import org.mifosng.platform.exceptions.ReportNotFoundException;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
@@ -46,15 +47,14 @@ import org.springframework.stereotype.Service;
public class ReadExtraDataAndReportingServiceImpl implements
ReadExtraDataAndReportingService {
private final static Logger logger = LoggerFactory
.getLogger(ReadExtraDataAndReportingServiceImpl.class);
private final static Logger logger = LoggerFactory.getLogger(ReadExtraDataAndReportingServiceImpl.class);
private final DataSource dataSource;
private final String reportingMetaDataDB;
private Boolean noPentaho = false;
@Autowired
public ReadExtraDataAndReportingServiceImpl(final DataSource dataSource) {
public ReadExtraDataAndReportingServiceImpl(final TenantAwareRoutingDataSource dataSource) {
// kick off pentaho reports server
ClassicEngineBoot.getInstance().start();
noPentaho = false;
@@ -6,8 +6,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
@@ -23,33 +21,34 @@ import org.mifosng.platform.client.domain.NoteEnumerations;
import org.mifosng.platform.exceptions.ClientNotFoundException;
import org.mifosng.platform.exceptions.NoteNotFoundException;
import org.mifosng.platform.infrastructure.JdbcSupport;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.organisation.service.OfficeReadPlatformService;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.mifosng.platform.user.service.AppUserReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class ClientReadPlatformServiceImpl implements ClientReadPlatformService {
private final SimpleJdbcTemplate jdbcTemplate;
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
private final OfficeReadPlatformService officeReadPlatformService;
private final AppUserReadPlatformService appUserReadPlatformService;
@Autowired
public ClientReadPlatformServiceImpl(final PlatformSecurityContext context,
final DataSource dataSource,
final TenantAwareRoutingDataSource dataSource,
final OfficeReadPlatformService officeReadPlatformService,
final AppUserReadPlatformService appUserReadPlatformService) {
this.context = context;
this.officeReadPlatformService = officeReadPlatformService;
this.appUserReadPlatformService = appUserReadPlatformService;
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private String generateOfficeIdInClause(List<OfficeData> offices) {
@@ -4,27 +4,26 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.infrastructure.JdbcSupport;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformService {
private final SimpleJdbcTemplate jdbcTemplate;
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
@Autowired
public CurrencyReadPlatformServiceImpl(final PlatformSecurityContext context, final DataSource dataSource) {
public CurrencyReadPlatformServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource) {
this.context = context;
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
@@ -4,29 +4,28 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import javax.sql.DataSource;
import org.mifosng.platform.api.data.FundData;
import org.mifosng.platform.exceptions.FundNotFoundException;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class FundReadPlatformServiceImpl implements FundReadPlatformService {
private final SimpleJdbcTemplate jdbcTemplate;
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
@Autowired
public FundReadPlatformServiceImpl(final PlatformSecurityContext context,
final DataSource dataSource) {
final TenantAwareRoutingDataSource dataSource) {
this.context = context;
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private static final class FundMapper implements RowMapper<FundData> {
@@ -0,0 +1,11 @@
package org.mifosng.platform.infrastructure;
import javax.sql.DataSource;
/**
* A service for getting hold of the appropriate {@link DataSource} connection pool for the given tenant.
*/
public interface DataSourcePerTenantService {
DataSource retrieveTenantAwareDataSource();
}
@@ -0,0 +1,14 @@
package org.mifosng.platform.infrastructure;
/**
* {@link RuntimeException} thrown when an invalid tenant identifier is used in request to platform.
*
* @see CustomRequestHeaderAuthenticationFilter
*/
public class InvalidTenantIdentiferException extends RuntimeException {
public InvalidTenantIdentiferException(final String message) {
super(message);
}
}
@@ -0,0 +1,50 @@
package org.mifosng.platform.infrastructure;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class JdbcTenantDetailsService implements TenantDetailsService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public JdbcTenantDetailsService(@Qualifier("tenantDataSourceJndi") final DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public MifosPlatformTenant loadTenantById(final String tenantIdentifier) {
try {
TenantMapper rm = new TenantMapper();
String sql = "select id, name, schema_name as schemaName from tenants t where t.identifier like ?";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] {tenantIdentifier});
} catch (EmptyResultDataAccessException e) {
throw new InvalidTenantIdentiferException("The tenant identifier: " + tenantIdentifier + " is not valid.");
}
}
private static final class TenantMapper implements RowMapper<MifosPlatformTenant> {
@Override
public MifosPlatformTenant mapRow(final ResultSet rs, final int rowNum) throws SQLException {
Long id = rs.getLong("id");
String name = rs.getString("name");
String schemaName = rs.getString("schemaName");
return new MifosPlatformTenant(id, name, schemaName);
}
}
}
@@ -0,0 +1,26 @@
package org.mifosng.platform.infrastructure;
public class MifosPlatformTenant {
private final Long id;
private final String name;
private final String schemaName;
public MifosPlatformTenant(Long id, String name, String schemaName) {
this.id = id;
this.name = name;
this.schemaName = schemaName;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getSchemaName() {
return schemaName;
}
}
@@ -0,0 +1,49 @@
package org.mifosng.platform.infrastructure;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
/**
*
*/
public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
private TenantDetailsService tenantDetailsService;
private String tenantRequestHeader = "X-Mifos-Platform-TenantId";
private boolean exceptionIfHeaderMissing = true;
public TenantAwareBasicAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationManager, authenticationEntryPoint);
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
final String tenantId = request.getHeader(tenantRequestHeader);
if (tenantId == null && exceptionIfHeaderMissing) {
throw new PreAuthenticatedCredentialsNotFoundException(tenantRequestHeader + " header not found in request.");
}
// check tenants database for tenantId
MifosPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantId);
ThreadLocalContextUtil.setTenant(tenant);
super.doFilter(req, res, chain);
}
}
@@ -0,0 +1,34 @@
package org.mifosng.platform.infrastructure;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.stereotype.Service;
/**
*
*/
@Service(value="tenantAwareDataSource")
public class TenantAwareRoutingDataSource extends AbstractDataSource {
@Autowired
private DataSourcePerTenantService dataSourcePerTenantService;
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
private DataSource determineTargetDataSource() {
return dataSourcePerTenantService.retrieveTenantAwareDataSource();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
}
@@ -0,0 +1,7 @@
package org.mifosng.platform.infrastructure;
public interface TenantDetailsService {
MifosPlatformTenant loadTenantById(String tenantId);
}
@@ -0,0 +1,24 @@
package org.mifosng.platform.infrastructure;
import org.springframework.util.Assert;
/**
*
*/
public class ThreadLocalContextUtil {
private static final ThreadLocal<MifosPlatformTenant> contextHolder = new ThreadLocal<MifosPlatformTenant>();
public static void setTenant(final MifosPlatformTenant tenant) {
Assert.notNull(tenant, "tenant cannot be null");
contextHolder.set(tenant);
}
public static MifosPlatformTenant getTenant() {
return contextHolder.get();
}
public static void clearTenant() {
contextHolder.remove();
}
}
@@ -0,0 +1,80 @@
package org.mifosng.platform.infrastructure;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.sql.DataSource;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
/**
*
*/
@Service
public class TomcatJdbcDataSourcePerTenantService implements DataSourcePerTenantService {
private final Map<Long, DataSource> tenantToDataSourceMap = new ConcurrentHashMap<Long, DataSource>(1);
private final DataSource tenantDataSource;
@Autowired
public TomcatJdbcDataSourcePerTenantService(final @Qualifier("tenantDataSourceJndi") DataSource tenantDataSource) {
this.tenantDataSource = tenantDataSource;
}
@Override
public DataSource retrieveTenantAwareDataSource() {
// default to tenant database datasource
DataSource tenantDataSource = this.tenantDataSource;
MifosPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
if (tenant != null) {
// if tenant information available switch to appropriate datasource for that tenant.
if (this.tenantToDataSourceMap.containsKey(tenant.getId())) {
tenantDataSource = this.tenantToDataSourceMap.get(tenant.getId());
} else {
tenantDataSource = createNewDataSourceFor(tenant);
this.tenantToDataSourceMap.put(tenant.getId(), tenantDataSource);
}
}
return tenantDataSource;
}
private DataSource createNewDataSourceFor(final MifosPlatformTenant tenant) {
// see http://www.tomcatexpert.com/blog/2010/04/01/configuring-jdbc-pool-high-concurrency
PoolConfiguration poolConfiguration = new PoolProperties();
poolConfiguration.setDriverClassName("com.mysql.jdbc.Driver");
poolConfiguration.setName(tenant.getSchemaName() + "_pool");
poolConfiguration.setUrl("jdbc:mysql://localhost:3306/" + tenant.getSchemaName());
poolConfiguration.setUsername("root");
poolConfiguration.setPassword("mysql");
poolConfiguration.setInitialSize(5);
poolConfiguration.setMaxActive(5);
poolConfiguration.setMinIdle(1);
poolConfiguration.setMaxIdle(4);
poolConfiguration.setSuspectTimeout(60);
poolConfiguration.setTimeBetweenEvictionRunsMillis(30000);
poolConfiguration.setMinEvictableIdleTimeMillis(60000);
poolConfiguration.setTestOnBorrow(true);
poolConfiguration.setValidationQuery("SELECT 1");
poolConfiguration.setValidationInterval(30000);
poolConfiguration.setRemoveAbandoned(true);
poolConfiguration.setRemoveAbandonedTimeout(60);
poolConfiguration.setLogAbandoned(true);
poolConfiguration.setAbandonWhenPercentageFull(50);
poolConfiguration.setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"
+ "org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer;org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport");
return new org.apache.tomcat.jdbc.pool.DataSource(poolConfiguration);
}
}
@@ -6,8 +6,6 @@ import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import javax.sql.DataSource;
import org.joda.time.DateTime;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.EnumOptionData;
@@ -19,6 +17,7 @@ import org.mifosng.platform.currency.service.CurrencyReadPlatformService;
import org.mifosng.platform.exceptions.LoanProductNotFoundException;
import org.mifosng.platform.fund.service.FundReadPlatformService;
import org.mifosng.platform.infrastructure.JdbcSupport;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.InterestCalculationPeriodMethod;
import org.mifosng.platform.loan.domain.InterestMethod;
@@ -27,8 +26,8 @@ import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Service;
@Service
@@ -37,7 +36,7 @@ public class LoanProductReadPlatformServiceImpl implements
private final PlatformSecurityContext context;
private final CurrencyReadPlatformService currencyReadPlatformService;
private final SimpleJdbcTemplate jdbcTemplate;
private final JdbcTemplate jdbcTemplate;
private final LoanDropdownReadPlatformService dropdownReadPlatformService;
private final FundReadPlatformService fundReadPlatformService;
@@ -47,12 +46,12 @@ public class LoanProductReadPlatformServiceImpl implements
final CurrencyReadPlatformService currencyReadPlatformService,
final FundReadPlatformService fundReadPlatformService,
final LoanDropdownReadPlatformService dropdownReadPlatformService,
final DataSource dataSource) {
final TenantAwareRoutingDataSource dataSource) {
this.context = context;
this.currencyReadPlatformService = currencyReadPlatformService;
this.fundReadPlatformService = fundReadPlatformService;
this.dropdownReadPlatformService = dropdownReadPlatformService;
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
@@ -6,33 +6,31 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.sql.DataSource;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.OfficeData;
import org.mifosng.platform.api.data.OfficeLookup;
import org.mifosng.platform.exceptions.OfficeNotFoundException;
import org.mifosng.platform.infrastructure.JdbcSupport;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService {
private final SimpleJdbcTemplate jdbcTemplate;
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
private final static String nameDecoratedBaseOnHierarchy = "concat(substring('........................................', 1, ((LENGTH(o.hierarchy) - LENGTH(REPLACE(o.hierarchy, '.', '')) - 1) * 4)), o.name)";
@Autowired
public OfficeReadPlatformServiceImpl(final PlatformSecurityContext context,
final DataSource dataSource) {
public OfficeReadPlatformServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource) {
this.context = context;
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private static final class OfficeMapper implements RowMapper<OfficeData> {
@@ -7,11 +7,13 @@ import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* Used in securityContext.xml as implementation of spring security's {@link UserDetailsService}.
*/
public class PlatformUserDetailsServiceImpl implements PlatformUserDetailsService {
@Service(value="userDetailsService")
public class TenantAwareJpaPlatformUserDetailsService implements PlatformUserDetailsService {
@Autowired
private PlatformUserRepository platformUserRepository;
@@ -9,42 +9,41 @@ import java.util.Collection;
import java.util.List;
import java.util.Set;
import javax.sql.DataSource;
import org.mifosng.platform.api.data.AppUserData;
import org.mifosng.platform.api.data.OfficeData;
import org.mifosng.platform.api.data.OfficeLookup;
import org.mifosng.platform.api.data.RoleData;
import org.mifosng.platform.exceptions.UserNotFoundException;
import org.mifosng.platform.infrastructure.JdbcSupport;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.organisation.service.OfficeReadPlatformService;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.mifosng.platform.user.domain.AppUserRepository;
import org.mifosng.platform.user.domain.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class AppUserReadPlatformServiceImpl implements AppUserReadPlatformService {
private final SimpleJdbcTemplate jdbcTemplate;
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
private final OfficeReadPlatformService officeReadPlatformService;
private final RoleReadPlatformService roleReadPlatformService;
private final AppUserRepository appUserRepository;
@Autowired
public AppUserReadPlatformServiceImpl(final PlatformSecurityContext context, final DataSource dataSource,
public AppUserReadPlatformServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource,
final OfficeReadPlatformService officeReadPlatformService, final RoleReadPlatformService roleReadPlatformService,
final AppUserRepository appUserRepository) {
this.context = context;
this.officeReadPlatformService = officeReadPlatformService;
this.roleReadPlatformService = roleReadPlatformService;
this.appUserRepository = appUserRepository;
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
@@ -4,27 +4,26 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import javax.sql.DataSource;
import org.mifosng.platform.api.data.PermissionData;
import org.mifosng.platform.infrastructure.JdbcSupport;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class PermissionReadPlatformServiceImpl implements PermissionReadPlatformService {
private final SimpleJdbcTemplate jdbcTemplate;
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
@Autowired
public PermissionReadPlatformServiceImpl(final PlatformSecurityContext context, final DataSource dataSource) {
public PermissionReadPlatformServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource) {
this.context = context;
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
@@ -6,32 +6,31 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import javax.sql.DataSource;
import org.mifosng.platform.api.data.RoleData;
import org.mifosng.platform.exceptions.RoleNotFoundException;
import org.mifosng.platform.infrastructure.JdbcSupport;
import org.mifosng.platform.infrastructure.TenantAwareRoutingDataSource;
import org.mifosng.platform.security.PlatformSecurityContext;
import org.mifosng.platform.user.domain.AppUser;
import org.mifosng.platform.user.domain.Role;
import org.mifosng.platform.user.domain.RoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class RoleReadPlatformServiceImpl implements RoleReadPlatformService {
private final SimpleJdbcTemplate jdbcTemplate;
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
private final RoleRepository roleRepository;
@Autowired
public RoleReadPlatformServiceImpl(final PlatformSecurityContext context, final DataSource dataSource, final RoleRepository roleRepository) {
public RoleReadPlatformServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource, final RoleRepository roleRepository) {
this.context = context;
this.roleRepository = roleRepository;
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="jpa-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
@@ -7,7 +10,7 @@
<!--
<property name="hibernate.hbm2ddl.auto" value="update"/>
-->
<property name="hibernate.jdbc.batch_size" value="200"/>
<property name="hibernate.jdbc.batch_size" value="100"/>
<property name="hibernate.order_inserts" value="true"/>
</properties>
</persistence-unit>
@@ -3,12 +3,10 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
@@ -17,8 +15,8 @@
<tx:annotation-driven />
<context:component-scan base-package="org.mifosng.platform.rest, org.mifosng.platform.security, org.mifosng.platform.infrastructure,
org.mifosng.platform.*.domain, org.mifosng.platform, org.mifosng.demo">
<context:component-scan base-package="org.mifosng.platform.security, org.mifosng.platform.infrastructure,
org.mifosng.platform.*.domain, org.mifosng.platform">
<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation" />
</context:component-scan>
@@ -7,7 +7,7 @@
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="dataSource" ref="tenantAwareDataSource" />
<property name="persistenceUnitName" value="jpa-pu" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
@@ -23,14 +23,5 @@
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<jee:jndi-lookup jndi-name="java:comp/env/jdbc/mifosng" id="dataSource" />
<!--
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mifosngprovider" />
<property name="username" value="root" />
<property name="password" value="mysql" />
</bean>
-->
<jee:jndi-lookup jndi-name="java:comp/env/jdbc/mifosplatform-tenants" id="tenantDataSourceJndi" />
</beans>
@@ -7,15 +7,25 @@
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<http create-session="stateless" use-expressions="true" pattern="/api/**" realm="Mifos Platform API">
<intercept-url pattern="/api/*/authentication" access="permitAll" method="POST" requires-channel="https" />
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="GET" requires-channel="https" />
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="POST" requires-channel="https"/>
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="PUT" requires-channel="https"/>
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="DELETE" requires-channel="https"/>
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="HEAD" requires-channel="https"/>
<http-basic />
<http create-session="stateless" use-expressions="true" pattern="/api/**" entry-point-ref="basicAuthenticationEntryPoint">
<intercept-url pattern="/api/*/authentication" access="permitAll" method="POST" requires-channel="http" />
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="GET" requires-channel="http" />
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="POST" requires-channel="http"/>
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="PUT" requires-channel="http"/>
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="DELETE" requires-channel="http"/>
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="HEAD" requires-channel="http"/>
<custom-filter after="SECURITY_CONTEXT_FILTER" ref="basicAuthenticationProcessingFilter" />
</http>
<beans:bean id="basicAuthenticationEntryPoint" class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
<beans:property name="realmName" value="Mifos Platform API" />
</beans:bean>
<beans:bean id="basicAuthenticationProcessingFilter" class="org.mifosng.platform.infrastructure.TenantAwareBasicAuthenticationFilter">
<beans:constructor-arg ref="authenticationManager" />
<beans:constructor-arg ref="basicAuthenticationEntryPoint" />
</beans:bean>
<beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
<beans:constructor-arg value="256"/>
@@ -25,8 +35,6 @@
<beans:property name="userPropertyToUse" value="id"/>
</beans:bean>
<beans:bean id="userDetailsService" class="org.mifosng.platform.security.PlatformUserDetailsServiceImpl" />
<beans:bean id="customAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="userDetailsService" />
<beans:property name="passwordEncoder" ref="passwordEncoder" />
@@ -7,7 +7,7 @@
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${catalina.base}/logs/mifosng-provider.log</file>
<file>${catalina.home}/logs/mifos-platform.log</file>
<append>true</append>
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<ResourceLink name="jdbc/mifosng" global="jdbc/mifosng" type="javax.sql.DataSource" />
<ResourceLink name="jdbc/mifosplatform-tenants" global="jdbc/mifosplatform-tenants" type="javax.sql.DataSource" />
</Context>
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:webflow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- The controllers are autodetected POJOs labeled with the @Controller annotation. -->
<context:component-scan base-package="org.mifosng.platform.security, org.mifosng.platform.infrastructure" use-default-filters="false">
<context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<bean id="mvcConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />
<!-- Turns on support for mapping requests to Spring MVC @Controller methods (DefaultAnnotationHandler is added to list of handlers)
Also registers default Formatters and Validators for use across all @Controllers -->
<mvc:annotation-driven conversion-service="mvcConversionService"/>
<mvc:default-servlet-handler />
<mvc:resources location="/WEB-INF/static/" mapping="/resources/**" cache-period="31556926"/>
<mvc:view-controller path="/index" view-name="index"/>
<mvc:view-controller path="/home" view-name="home"/>
<mvc:view-controller path="/login" view-name="login"/>
<mvc:view-controller path="/logout" view-name="logout"/>
<!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory -->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
@@ -5,12 +5,12 @@
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="mifosng-platform-provider" version="2.5">
<display-name>MifosNg - Platform</display-name>
<display-name>Mifos Platform</display-name>
<description>A provider of microfinance services which applications can consume once authenticated and authorized.</description>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>mifosngserver.root</param-value>
<param-value>mifosplatform.root</param-value>
</context-param>
<context-param>
@@ -28,26 +28,10 @@
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
<filter>
<filter-name>filterChainProxy</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>filterChainProxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
@@ -77,30 +61,4 @@
<servlet-name>jersey-serlvet</servlet-name>
<url-pattern>/api/v1/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/webmvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<trim-directive-whitespaces>true</trim-directive-whitespaces>
</jsp-property-group>
</jsp-config>
<session-config>
<session-timeout>20</session-timeout>
</session-config>
</web-app>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource type="javax.sql.DataSource"
name="jdbc/mifosng"
name="jdbc/mifosplatform-tenants"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/mifosngprovider"
url="jdbc:mysql://localhost:3306/mifosplatform-tenants"
username="root"
password="mysql"
validationQuery="select 1"