MIFOSX-1746: Add support for Oauth2 and make it build time configurable

This commit is contained in:
Adi Raju
2015-09-08 11:42:34 +05:30
parent 07f8812957
commit c9767e76d7
21 changed files with 1077 additions and 58 deletions
+1
View File
@@ -22,3 +22,4 @@ rebel.xml
*.gradletasknamecache *.gradletasknamecache
*~ *~
.metadata .metadata
mifosng-provider\src\main\resources\application.properties
+377 -8
View File
@@ -104,6 +104,8 @@
<li><a href="#dates_and_numbers">Updating Dates and Numbers</a></li> <li><a href="#dates_and_numbers">Updating Dates and Numbers</a></li>
<li><a href="#field_descriptions">Field Descriptions</a></li> <li><a href="#field_descriptions">Field Descriptions</a></li>
<li><a href="#authentication_overview">Authentication Overview</a></li> <li><a href="#authentication_overview">Authentication Overview</a></li>
<li><a href="#authentication_basicauth">Authentication HTTP Basic</a></li>
<li><a href="#authentication_oauth">Authentication Oauth2</a></li>
<li><a href="#errors">Errors</a></li> <li><a href="#errors">Errors</a></li>
<li><a href="#batch_api">Batch API</a></li> <li><a href="#batch_api">Batch API</a></li>
<li><a href="#fullapi_matrix">Full API Matrix</a></li> <li><a href="#fullapi_matrix">Full API Matrix</a></li>
@@ -2141,13 +2143,37 @@
</table></td> </table></td>
</tr> </tr>
<tr> <tr>
<td><a href="#authentication">Authentication</a></td> <td><a href="#authenticationbasic">Authentication HTTP Basic</a></td>
<td>authentication</td> <td>authentication</td>
<td><a href="#authenticate_request">Verify Authentication</a></td> <td><a href="#authenticate_request_basic">Verify Authentication</a></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td><a href="#authenticationoauth">Authentication Oauth2</a></td>
<td>oauth/token</td>
<td><a href="#oauth">OAuth2 Access and refresh Token Request</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td><a href="#oauth_access_token_req">OAuth2 Access Token Request from refresh token</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>authenticated user</td>
<td></td>
<td><a href="#userdetails_request">Fetch Authenticated user details</a></td>
<td></td>
<td></td>
</tr>
<tr> <tr>
<td><a href="#users">User</a></td> <td><a href="#users">User</a></td>
<td>users</td> <td>users</td>
@@ -3039,11 +3065,14 @@
<div class="method-description"> <div class="method-description">
<h3>Authentication Overview</h3> <h3>Authentication Overview</h3>
<p> <p>
Authentication to the API occurs via <a Authentication to the API can be configured to be supported via <a
href="http://en.wikipedia.org/wiki/Basic_access_authentication">HTTP href="http://en.wikipedia.org/wiki/Basic_access_authentication">HTTP
Basic Auth</a>. Basic Auth</a> or <a href="http://tools.ietf.org/html/rfc6749">OAuth2</a>.
</p> </p>
<p>
Default authentication is using HTTP Basic Auth. Oauth2 can be enabled by using <i>-Psecurity=oauth</i> option on gradle build command.
</p>
<p> <p>
The platform has been configured to reject plain HTTP requests and The platform has been configured to reject plain HTTP requests and
to expect all API requests to be made over <a to expect all API requests to be made over <a
@@ -3053,6 +3082,26 @@
</div> </div>
<div class="method-example"> <div class="method-example">
<code> <code>
For eg.
Use 'gradlew clean tomcatRunWar' for HTTP Basic Auth and use 'gradlew clean tomcatRunWar -Psecurity=oauth' for Oauth2 Authentication.
</code>
</div>
</div>
<a id="authentication_basicauth" name="authentication_basicauth"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Authentication HTTP Basic</h3>
<p>
Authentication to the API occurs via <a
href="http://en.wikipedia.org/wiki/Basic_access_authentication">HTTP
Basic Auth</a>.
</p>
</div>
<div class="method-example">
<code>
// A Javascript/Jquery example of how to login to Mifos X and use its api. // A Javascript/Jquery example of how to login to Mifos X and use its api.
// Typically, the javascript application would // Typically, the javascript application would
// 1) Display a login page to retrieve the username and password. // 1) Display a login page to retrieve the username and password.
@@ -3098,6 +3147,63 @@ function executeAjaxRequest(url, verbType, jsonData, basicAuthKey, successFuncti
</div> </div>
</div> </div>
<a id="authentication_oauth" name="authentication_oauth"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Authentication Oauth2</h3>
<p>
Authentication to the API occurs via <a href="http://tools.ietf.org/html/rfc6749">OAuth2</a>. 'Resource Owner Password Credentials Grant' type is used.
</p>
</div>
<div class="method-example">
<code>
// A Javascript/Jquery example of how to login to Mifos X and use its api.
// Typically, the javascript application would
// 1) Display a login page to retrieve the username and password.
// 2) Send the username, password, client_id , grant_type and client_secret to a function
// such as getOauthToken below which sets the HTTP bearer Auth key.
// 3) The HTTP bearer Auth key is used in all subsequent requests
// (see the function executeAjaxRequest below).
function getOauthToken(username, password) {
var jqxhr = $.ajax({
url : "/mifosng-provider/api/oauth/token?username=" + credentials.username + "&password=" + credentials.password +"&client_id=community-app&grant_type=password&client_secret=123,
type : 'POST',
contentType : "application/json; charset=utf-8",
dataType : 'json',
data : "{}",
cache : false,
success : function(data, textStatus, jqXHR) {
authKey = data.access_token;
},
error : function(jqXHR, textStatus, errorThrown) {
//error processing
}
});
}
function executeAjaxRequest(url, verbType, jsonData, authKey, successFunction, errorFunction) {
var jqxhr = $.ajax({
url : url,
type : verbType, //POST, GET, PUT or DELETE
contentType : "application/json; charset=utf-8",
dataType : 'json',
data : jsonData,
cache : false,
beforeSend : function(xhr) {
xhr.setRequestHeader("Authorization", "bearer " + authKey);
},
success : successFunction,
error : errorFunction
});
}
</code>
</div>
</div>
<a id="batch_api" name="batch_api" class="old-syle-anchor">&nbsp;</a> <a id="batch_api" name="batch_api" class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
<div class="method-description"> <div class="method-description">
@@ -30584,12 +30690,12 @@ GET https://DomainName/api/v1/runreports/{reportName}
</div> </div>
<!-- start of authentication api docs --> <!-- start of authentication api docs -->
<a id="authentication" name="authentication" class="old-syle-anchor">&nbsp;</a> <a id="authenticationbasic" name="authenticationbasic" class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
<div class="method-description"> <div class="method-description">
<h3>Authentication</h3> <h3>Authentication HTTP Basic</h3>
<p>An API capability that allows client applications to verify <p>An API capability that allows client applications to verify
authentication details.</p> authentication details using HTTP Basic Authentication.</p>
<table class=matrixHeading> <table class=matrixHeading>
<tr class="matrixHeadingBG"> <tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td> <td><div class="mifosXHeading2">Field Descriptions</div></td>
@@ -30608,7 +30714,7 @@ GET https://DomainName/api/v1/runreports/{reportName}
</div> </div>
</div> </div>
<a id="authenticate_request" name="authenticate_request" class="old-syle-anchor">&nbsp;</a> <a id="authenticate_request_basic" name="authenticate_request_basic" class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
<div class="method-description"> <div class="method-description">
<h4>Verify authentication</h4> <h4>Verify authentication</h4>
@@ -30695,6 +30801,269 @@ No Request Body
</div> </div>
</div> </div>
<a id="authenticationoauth" name="authenticationoauth" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Authentication Oauth2</h3>
<p>An API capability that allows client applications to fetch current user details details using Oauth2.</p>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td>
</tr>
<tr class=alt>
<td>accessToken</td>
</tr>
<tr>
<td class=fielddesc>HTTP Auth bearer key. See <a
href="#authentication_overview">Authentication Overview</a> for
an example of its use.
</td>
</tr>
</tr>
</table>
</div>
</div>
<a id="oauth" name="oauth" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Authentication via OAuth2</h3>
<p>API for requesting OAuth2 access token and refresh token.</p>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td>
</tr>
<tr class=alt>
<td>grant_type</td>
</tr>
<tr>
<td class=fielddesc>Mandatory field.
Indicates the requested grant type. Supported values:
<span>password</span>.
</td>
</tr>
<tr class=alt>
<td>client_id</td>
</tr>
<tr>
<td class=fielddesc>Mandatory field.
Indicates the client application identity.
</td>
</tr>
<tr class=alt>
<td>client_secret</td>
</tr>
<tr>
<td class=fielddesc>Optional field.
Indicates the client application password.
</td>
</tr>
<tr class=alt>
<td>username</td>
</tr>
<tr>
<td class=fielddesc>Mandatory field.
Application User(resource) login name.
</td>
</tr>
<tr class=alt>
<td>password</td>
</tr>
<tr>
<td class=fielddesc>Mandatory field.
Application User(resource) password.
</td>
</tr>
</table>
</div>
</div>
<a id="oauth_request" name="oauth_request" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>OAuth2 Refresh and Access Token Request</h4>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/oauth/token?username={username}&password={password}&client_id={clientId}&grant_type={grant_type}&client_secret={client_secret}
</code>
<code class="method-request">
POST api/oauth/token?username=mifos&password=password&client_id=community-app&grant_type=password&client_secret=123
Content-Type: application/json
No Request Body
</code>
<code class="method-response">
{
""access_token": "b771987f-82fc-45ba-b521-bfe280c4e603",
"token_type": "bearer",
"refresh_token":"a2a89b23-8d22-4d90-8585-8f464db433b0",
"expires_in": 3599,
"scope": "all"
}
</code>
</div>
</div>
<a id="oauth_access_token_req" name="oauth_access_token_req" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Authentication via OAuth2</h3>
<p>API for requesting OAuth2 access tokens through oAuth2 refresh tokens.</p>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td>
</tr>
<tr class=alt>
<td>grant_type</td>
</tr>
<tr>
<td class=fielddesc>Mandatory field.
Indicates the requested grant type. Supported values:
<span>refresh_token</span>.
</td>
</tr>
<tr class=alt>
<td>client_id</td>
</tr>
<tr>
<td class=fielddesc>Mandatory field.
Indicates the client application identity.
</td>
</tr>
<tr class=alt>
<td>client_secret</td>
</tr>
<tr>
<td class=fielddesc>Optional field.
Indicates the client application password.
</td>
</tr>
<tr class=alt>
<td>refresh_token</td>
</tr>
<tr>
<td class=fielddesc>Mandatory field.
Application refresh token to generate access token.
</td>
</tr>
</table>
</div>
</div>
<a id="oauth_request_with_refresh" name="oauth_request_with_refresh" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>OAuth2 Token Request through Refresh Token</h4>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/oauth/token?refresh_token={refresh_token}&client_id={clientId}&grant_type={grant_type}&client_secret={client_secret}
</code>
<code class="method-request">
POST api/oauth/token?client_id=community-app&grant_type=refresh_token&client_secret=123&refresh_token=a2a89b23-8d22-4d90-8585-8f464db433b0
Content-Type: application/json
No Request Body
</code>
<code class="method-response">
{
""access_token": "b771987f-82fc-45ba-b521-bfe280c4e643",
"token_type": "bearer",
"refresh_token":"a2a89b23-8d22-4d90-8585-8f464db433b0",
"expires_in": 3599,
"scope": "all"
}
</code>
</div>
</div>
<a id="userdetails_request" name="userdetails_request" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Fetch authenticated user details </h4>
<p>checks the Authentication and returns the set roles and permissions allowed.</p>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/userdetails?access_token={access_token}
</code>
<code class="method-request">
POST userdetails?access_token=bWlmb3M6cGFzc3dvcmQ=
Content-Type: application/json
No Request Body
</code>
<p>Example response of authenticated user user that is not linked with any staff.</p>
<code class="method-response">
{
"username": "mifos",
"userId": 1,
"accessToken": "bWlmb3M6cGFzc3dvcmQ=",
"authenticated": true,
"officeId": 1,
"officeName": "Head Office",
"roles": [
{
"id": 1,
"name": "Super user",
"description": "This role provides all application permissions."
}
],
"permissions": [
"ALL_FUNCTIONS"
]
}
</code>
<p>Example response of authenticated user that is linked with a staff member and role.</p>
<code class="method-response">
{
"username": "mifos",
"userId": 1,
"accessToken": "bWlmb3M6cGFzc3dvcmQ=",
"authenticated": true,
"officeId": 1,
"officeName": "Head Office",
"staffId": 1,
"staffDisplayName": "Director, Program",
"organisationalRole": {
"id": 100,
"code": "staffOrganisationalRoleType.programDirector",
"value": "Program Director"
},
"roles": [
{
"id": 1,
"name": "Super user",
"description": "This role provides all application permissions."
}
],
"permissions": [
"ALL_FUNCTIONS"
]
}
</code>
<code class="method-request">
POST api/oauth/token?username=mifos&password=password&client_id=community-app&grant_type=password&client_secret=123
Content-Type: application/json
No Request Body
</code>
<code class="method-response">
{
"developerMessage": "Invalid authentication details were passed in api request.",
"developerDocLink": "https://github.com/openMF/mifosx/wiki/HTTP-API-Error-codes",
"httpStatusCode": "401",
"defaultUserMessage": "Unauthenticated. Please login.",
"userMessageGlobalisationCode": "error.msg.not.authenticated",
"errors": []
}
</code>
</div>
</div>
<!-- start of users api docs --> <!-- start of users api docs -->
<a id="users" name="users" class="old-syle-anchor">&nbsp;</a> <a id="users" name="users" class="old-syle-anchor">&nbsp;</a>
<div class="method-section"> <div class="method-section">
+16
View File
@@ -33,6 +33,7 @@ targetCompatibility = JavaVersion.VERSION_1_7
project.ext.springBootVersion = '1.1.6.RELEASE' project.ext.springBootVersion = '1.1.6.RELEASE'
project.ext.springVersion = '4.0.7.RELEASE' project.ext.springVersion = '4.0.7.RELEASE'
project.ext.springOauthVersion = '2.0.4.RELEASE'
project.ext.jerseyVersion = '1.17' project.ext.jerseyVersion = '1.17'
project.ext.springDataJpaVersion = '1.7.0.RELEASE' // also change spring-boot-gradle-plugin version above project.ext.springDataJpaVersion = '1.7.0.RELEASE' // also change spring-boot-gradle-plugin version above
@@ -64,6 +65,21 @@ if (project.hasProperty('env') && project.getProperty('env') == 'dev') {
apply from: 'dependencies.gradle' apply from: 'dependencies.gradle'
} }
/* Enable Oauth2 authentication based on environment, default to HTTP basic auth */
if (project.hasProperty('security') && project.getProperty('security') == 'oauth') {
copy {
from './properties/oauth/'
into 'src/main/resources/'
include '*.properties'
}
} else {
copy {
from './properties/basicauth/'
into 'src/main/resources/'
include '*.properties'
}
}
task dist(type:Zip){ task dist(type:Zip){
baseName = 'mifosplatform' baseName = 'mifosplatform'
version = qualifyVersionIfNecessary(releaseVersion) version = qualifyVersionIfNecessary(releaseVersion)
+2
View File
@@ -21,6 +21,8 @@ dependencies {
[group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: springBootVersion], [group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: springBootVersion],
[group: 'org.springframework', name: 'spring-context-support', version: springVersion], [group: 'org.springframework', name: 'spring-context-support', version: springVersion],
[group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: springOauthVersion],
[group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'], [group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'],
[group: 'com.sun.jersey', name: 'jersey-core', version: jerseyVersion], [group: 'com.sun.jersey', name: 'jersey-core', version: jerseyVersion],
+1
View File
@@ -21,6 +21,7 @@ dependencies {
[group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: springBootVersion], [group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: springBootVersion],
[group: 'org.springframework', name: 'spring-context-support', version: springVersion], [group: 'org.springframework', name: 'spring-context-support', version: springVersion],
[group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: springOauthVersion],
[group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'], [group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'],
[group: 'com.sun.jersey', name: 'jersey-core', version: jerseyVersion], [group: 'com.sun.jersey', name: 'jersey-core', version: jerseyVersion],
@@ -0,0 +1,7 @@
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#
spring.profiles.default=basicauth
@@ -0,0 +1,8 @@
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#
spring.profiles.default=basicauth
spring.profiles.active=oauth
@@ -26,7 +26,7 @@ import org.springframework.context.annotation.ImportResource;
* and MariaDB4j (because those differ in the subclasses). * and MariaDB4j (because those differ in the subclasses).
*/ */
@Configuration @Configuration
@Import({ WebXmlConfiguration.class, WebFrontEndConfiguration.class }) @Import({ WebXmlConfiguration.class, WebXmlOauthConfiguration.class, WebFrontEndConfiguration.class })
@ImportResource({ "classpath*:META-INF/spring/appContext.xml" }) @ImportResource({ "classpath*:META-INF/spring/appContext.xml" })
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
@@ -15,6 +15,7 @@ import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.context.embedded.ServletRegistrationBean; import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.filter.DelegatingFilterProxy; import org.springframework.web.filter.DelegatingFilterProxy;
import com.sun.jersey.spi.spring.container.servlet.SpringServlet; import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
@@ -26,6 +27,7 @@ import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
* href="http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-convert-an-existing-application-to-spring-boot">#howto-convert-an-existing-application-to-spring-boot</a> * href="http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-convert-an-existing-application-to-spring-boot">#howto-convert-an-existing-application-to-spring-boot</a>
*/ */
@Configuration @Configuration
@Profile("basicauth")
public class WebXmlConfiguration { public class WebXmlConfiguration {
@Autowired @Autowired
@@ -0,0 +1,61 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;
import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
/**
* This Configuration replaces what formerly was in web.xml. Beans are loaded only when "oauth" Profile is enabled.
*
* @see <a
* href="http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-convert-an-existing-application-to-spring-boot">#howto-convert-an-existing-application-to-spring-boot</a>
*/
@Configuration
@Profile("oauth")
public class WebXmlOauthConfiguration {
@Bean
public Filter springSecurityFilterChain() {
return new DelegatingFilterProxy();
}
@Bean
public ServletRegistrationBean jersey() {
Servlet jerseyServlet = new SpringServlet();
ServletRegistrationBean jerseyServletRegistration = new ServletRegistrationBean();
jerseyServletRegistration.setServlet(jerseyServlet);
jerseyServletRegistration.addUrlMappings("/api/v1/*");
jerseyServletRegistration.setName("jersey-servlet");
jerseyServletRegistration.setLoadOnStartup(1);
jerseyServletRegistration.addInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
// jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerResponseFilters",
// ResponseCorsFilter.class.getName());
jerseyServletRegistration.addInitParameter("com.sun.jersey.config.feature.DisableWADL", "true");
// debugging for development:
// jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters",
// LoggingFilter.class.getName());
return jerseyServletRegistration;
}
@Bean
public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(dispatcherServlet);
registrationBean.addUrlMappings("/api/oauth/token");
return registrationBean;
}
}
@@ -24,6 +24,7 @@ import org.mifosplatform.useradministration.domain.AppUser;
import org.mifosplatform.useradministration.domain.Role; import org.mifosplatform.useradministration.domain.Role;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
@@ -35,6 +36,7 @@ import com.sun.jersey.core.util.Base64;
@Path("/authentication") @Path("/authentication")
@Component @Component
@Profile("basicauth")
@Scope("singleton") @Scope("singleton")
public class AuthenticationApiResource { public class AuthenticationApiResource {
@@ -0,0 +1,98 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.security.api;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.data.AuthenticatedOauthUserData;
import org.mifosplatform.infrastructure.security.service.SpringSecurityPlatformSecurityContext;
import org.mifosplatform.useradministration.data.RoleData;
import org.mifosplatform.useradministration.domain.AppUser;
import org.mifosplatform.useradministration.domain.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Scope;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.stereotype.Component;
/*
* Implementation of Oauth2 authentication APIs, loaded only when "oauth" profile is enabled.
*/
@Path("/userdetails")
@Component
@Profile("oauth")
@Scope("singleton")
public class UserDetailsApiResource {
private final ResourceServerTokenServices tokenServices;
private final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService;
private final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext;
@Autowired
public UserDetailsApiResource(@Qualifier("tokenServices") final ResourceServerTokenServices tokenServices,
final ToApiJsonSerializer<AuthenticatedOauthUserData> apiJsonSerializerService,
final SpringSecurityPlatformSecurityContext springSecurityPlatformSecurityContext) {
this.tokenServices = tokenServices;
this.apiJsonSerializerService = apiJsonSerializerService;
this.springSecurityPlatformSecurityContext = springSecurityPlatformSecurityContext;
}
@GET
@Produces({ MediaType.APPLICATION_JSON })
public String fetchAuthenticatedUserData(@QueryParam("access_token") final String accessToken) {
final Authentication authentication = this.tokenServices.loadAuthentication(accessToken);
if (authentication.isAuthenticated()) {
final AppUser principal = (AppUser) authentication.getPrincipal();
final Collection<String> permissions = new ArrayList<>();
AuthenticatedOauthUserData authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), permissions);
final Collection<GrantedAuthority> authorities = new ArrayList<>(authentication.getAuthorities());
for (final GrantedAuthority grantedAuthority : authorities) {
permissions.add(grantedAuthority.getAuthority());
}
final Collection<RoleData> roles = new ArrayList<>();
final Set<Role> userRoles = principal.getRoles();
for (final Role role : userRoles) {
roles.add(role.toData());
}
final Long officeId = principal.getOffice().getId();
final String officeName = principal.getOffice().getName();
final Long staffId = principal.getStaffId();
final String staffDisplayName = principal.getStaffDisplayName();
final EnumOptionData organisationalRole = principal.organisationalRoleData();
if (this.springSecurityPlatformSecurityContext.doesPasswordHasToBeRenewed(principal)) {
authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), principal.getId(), accessToken);
} else {
authenticatedUserData = new AuthenticatedOauthUserData(principal.getUsername(), officeId, officeName, staffId, staffDisplayName,
organisationalRole, roles, permissions, principal.getId(), accessToken);
}
return this.apiJsonSerializerService.serialize(authenticatedUserData);
}
return null;
}
}
@@ -0,0 +1,90 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.security.data;
import java.util.Collection;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.useradministration.data.RoleData;
/**
* Immutable data object for authentication. Used in case of Oauth2.
*/
public class AuthenticatedOauthUserData {
@SuppressWarnings("unused")
private final String username;
@SuppressWarnings("unused")
private final Long userId;
@SuppressWarnings("unused")
private final String accessToken;
@SuppressWarnings("unused")
private final boolean authenticated;
@SuppressWarnings("unused")
private final Long officeId;
@SuppressWarnings("unused")
private final String officeName;
@SuppressWarnings("unused")
private final Long staffId;
@SuppressWarnings("unused")
private final String staffDisplayName;
@SuppressWarnings("unused")
private final EnumOptionData organisationalRole;
@SuppressWarnings("unused")
private final Collection<RoleData> roles;
@SuppressWarnings("unused")
private final Collection<String> permissions;
@SuppressWarnings("unused")
private final boolean shouldRenewPassword;
public AuthenticatedOauthUserData(final String username, final Collection<String> permissions) {
this.username = username;
this.userId = null;
this.accessToken = null;
this.authenticated = false;
this.officeId = null;
this.officeName = null;
this.staffId = null;
this.staffDisplayName = null;
this.organisationalRole = null;
this.roles = null;
this.permissions = permissions;
this.shouldRenewPassword = false;
}
public AuthenticatedOauthUserData(final String username, final Long officeId, final String officeName, final Long staffId,
final String staffDisplayName, final EnumOptionData organisationalRole, final Collection<RoleData> roles,
final Collection<String> permissions, final Long userId, final String accessToken) {
this.username = username;
this.officeId = officeId;
this.officeName = officeName;
this.staffId = staffId;
this.staffDisplayName = staffDisplayName;
this.organisationalRole = organisationalRole;
this.userId = userId;
this.accessToken = accessToken;
this.authenticated = true;
this.roles = roles;
this.permissions = permissions;
this.shouldRenewPassword = false;
}
public AuthenticatedOauthUserData(final String username, final Long userId, final String accessToken) {
this.username = username;
this.officeId = null;
this.officeName = null;
this.staffId = null;
this.staffDisplayName = null;
this.organisationalRole = null;
this.userId = userId;
this.accessToken = accessToken;
this.authenticated = true;
this.roles = null;
this.permissions = null;
this.shouldRenewPassword = true;
}
}
@@ -27,6 +27,7 @@ import org.mifosplatform.infrastructure.security.service.BasicAuthTenantDetailsS
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
@@ -49,6 +50,7 @@ import org.springframework.stereotype.Service;
* is returned. * is returned.
*/ */
@Service(value = "basicAuthenticationProcessingFilter") @Service(value = "basicAuthenticationProcessingFilter")
@Profile("basicauth")
public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFilter { public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFilter {
private static boolean firstRequestProcessed = false; private static boolean firstRequestProcessed = false;
@@ -0,0 +1,148 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.security.filter;
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 javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.time.StopWatch;
import org.mifosplatform.infrastructure.cache.domain.CacheType;
import org.mifosplatform.infrastructure.cache.service.CacheWritePlatformService;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.security.data.PlatformRequestLog;
import org.mifosplatform.infrastructure.security.exception.InvalidTenantIdentiferException;
import org.mifosplatform.infrastructure.security.service.BasicAuthTenantDetailsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.web.filter.GenericFilterBean;
/**
*
* This filter is responsible for extracting multi-tenant from the request and
* setting Cross-Origin details to response.
*
* If multi-tenant are valid, the details of the tenant are stored in
* {@link MifosPlatformTenant} and stored in a {@link ThreadLocal} variable for
* this request using {@link ThreadLocalContextUtil}.
*
* If multi-tenant are invalid, a http error response is returned.
*
* Used to support Oauth2 authentication and the service is loaded only when "oauth" profile is active.
*/
@Service(value = "tenantIdentifierProcessingFilter")
@Profile("oauth")
public class TenantAwareTenantIdentifierFilter extends GenericFilterBean {
private static boolean firstRequestProcessed = false;
private final static Logger logger = LoggerFactory.getLogger(TenantAwareTenantIdentifierFilter.class);
private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;
private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
private final ConfigurationDomainService configurationDomainService;
private final CacheWritePlatformService cacheWritePlatformService;
private final String tenantRequestHeader = "X-Mifos-Platform-TenantId";
private final boolean exceptionIfHeaderMissing = true;
private final String apiUri = "/api/v1/";
@Autowired
public TenantAwareTenantIdentifierFilter(final BasicAuthTenantDetailsService basicAuthTenantDetailsService,
final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer, final ConfigurationDomainService configurationDomainService,
final CacheWritePlatformService cacheWritePlatformService) {
this.basicAuthTenantDetailsService = basicAuthTenantDetailsService;
this.toApiJsonSerializer = toApiJsonSerializer;
this.configurationDomainService = configurationDomainService;
this.cacheWritePlatformService = cacheWritePlatformService;
}
@Override
public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
final StopWatch task = new StopWatch();
task.start();
try {
// allows for Cross-Origin
// Requests (CORs) to be performed against the platform API.
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
final String reqHead = request.getHeader("Access-Control-Request-Headers");
if (null != reqHead && !reqHead.equals(null)) {
response.setHeader("Access-Control-Allow-Headers", reqHead);
}
if (!"OPTIONS".equalsIgnoreCase(request.getMethod())) {
String tenantIdentifier = request.getHeader(this.tenantRequestHeader);
if (org.apache.commons.lang.StringUtils.isBlank(tenantIdentifier)) {
tenantIdentifier = request.getParameter("tenantIdentifier");
}
if (tenantIdentifier == null && this.exceptionIfHeaderMissing) { throw new InvalidTenantIdentiferException(
"No tenant identifier found: Add request header of '" + this.tenantRequestHeader
+ "' or add the parameter 'tenantIdentifier' to query string of request URL."); }
String pathInfo = request.getRequestURI();
boolean isReportRequest = false;
if (pathInfo != null && pathInfo.contains("report")) {
isReportRequest = true;
}
final MifosPlatformTenant tenant = this.basicAuthTenantDetailsService.loadTenantById(tenantIdentifier, isReportRequest);
ThreadLocalContextUtil.setTenant(tenant);
String authToken = request.getHeader("Authorization");
if (authToken != null && authToken.startsWith("bearer ")) {
ThreadLocalContextUtil.setAuthToken(authToken.replaceFirst("bearer ", ""));
}
if (!firstRequestProcessed) {
final String baseUrl = request.getRequestURL().toString()
.replace(request.getRequestURI(), request.getContextPath() + apiUri);
System.setProperty("baseUrl", baseUrl);
final boolean ehcacheEnabled = this.configurationDomainService.isEhcacheEnabled();
if (ehcacheEnabled) {
this.cacheWritePlatformService.switchToCache(CacheType.SINGLE_NODE);
} else {
this.cacheWritePlatformService.switchToCache(CacheType.NO_CACHE);
}
TenantAwareTenantIdentifierFilter.firstRequestProcessed = true;
}
chain.doFilter(request, response);
}
} catch (final InvalidTenantIdentiferException e) {
// deal with exception at low level
SecurityContextHolder.getContext().setAuthentication(null);
response.addHeader("WWW-Authenticate", "Basic realm=\"" + "Mifos Platform API" + "\"");
response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
} finally {
task.stop();
final PlatformRequestLog log = PlatformRequestLog.from(task, request);
logger.info(this.toApiJsonSerializer.serialize(log));
}
}
}
@@ -11,55 +11,210 @@
You can obtain one at http://mozilla.org/MPL/2.0/. --> You can obtain one at http://mozilla.org/MPL/2.0/. -->
<beans:beans xmlns="http://www.springframework.org/schema/security" <beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation=" xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/beans
http://www.springframework.org/schema/security/spring-security-3.2.xsd http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/security/oauth2
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"> http://www.springframework.org/schema/security/spring-security-oauth2.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<http create-session="stateless" use-expressions="true" pattern="/api/**" <beans:beans profile="basicauth">
entry-point-ref="basicAuthenticationEntryPoint"> <http create-session="stateless" use-expressions="true" pattern="/api/**"
<intercept-url pattern="/api/*/authentication" access="permitAll" entry-point-ref="basicAuthenticationEntryPoint">
method="POST" requires-channel="https" /> <intercept-url pattern="/api/*/authentication" access="permitAll"
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="POST" requires-channel="https" />
method="GET" requires-channel="https" /> <intercept-url pattern="/api/**" access="isFullyAuthenticated()"
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="GET" requires-channel="https" />
method="POST" requires-channel="https" /> <intercept-url pattern="/api/**" access="isFullyAuthenticated()"
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="POST" requires-channel="https" />
method="PUT" requires-channel="https" /> <intercept-url pattern="/api/**" access="isFullyAuthenticated()"
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="PUT" requires-channel="https" />
method="DELETE" requires-channel="https" /> <intercept-url pattern="/api/**" access="isFullyAuthenticated()"
<intercept-url pattern="/api/**" access="isFullyAuthenticated()" method="DELETE" requires-channel="https" />
method="HEAD" requires-channel="https" /> <intercept-url pattern="/api/**" access="isFullyAuthenticated()"
method="HEAD" requires-channel="https" />
<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="passwordEncoder"
class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
<beans:constructor-arg value="256" />
</beans:bean>
<beans:bean id="saltSource"
class="org.springframework.security.authentication.dao.ReflectionSaltSource">
<beans:property name="userPropertyToUse" value="id" />
</beans:bean>
<beans:bean id="customAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="userDetailsService" />
<beans:property name="passwordEncoder" ref="passwordEncoder" />
<beans:property name="saltSource" ref="saltSource" />
</beans:bean>
<authentication-manager alias="authenticationManager"
erase-credentials="false">
<authentication-provider ref="customAuthenticationProvider" />
</authentication-manager>
</beans:beans>
<beans:beans profile="oauth">
<http create-session="stateless" use-expressions="true" pattern="/api/v1/**"
entry-point-ref="oauthAuthenticationEntryPoint"
access-decision-manager-ref="accessDecisionManager">
<anonymous enabled="false" />
<intercept-url pattern="/api//v1/**" method="OPTIONS"
access="permitAll" requires-channel="https" />
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
method="GET" requires-channel="https" />
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
method="POST" requires-channel="https" />
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
method="PUT" requires-channel="https" />
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
method="DELETE" requires-channel="https" />
<intercept-url pattern="/api/v1/**" access="isFullyAuthenticated()"
method="HEAD" requires-channel="https" />
<custom-filter ref="tenantIdentifierProcessingFilter"
position="FIRST" />
<custom-filter before="PRE_AUTH_FILTER" ref="resourceServerFilter" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<http pattern="/api/oauth/token" create-session="stateless"
entry-point-ref="oauthAuthenticationEntryPoint" use-expressions="true"
authentication-manager-ref="clientAuthenticationManager">
<intercept-url pattern="/api/oauth/token" method="OPTIONS"
access="permitAll" requires-channel="https" />
<intercept-url pattern="/api/oauth/token" method="POST"
access="isFullyAuthenticated()" requires-channel="https" />
<anonymous enabled="false" />
<custom-filter ref="tenantIdentifierProcessingFilter"
position="FIRST" />
<custom-filter ref="clientCredentialsTokenEndpointFilter"
before="BASIC_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<beans:bean id="oauthAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<beans:property name="realmName" value="Mifos Platform API" />
</beans:bean>
<beans:bean id="oauthAccessDeniedHandler"
class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
<beans:bean id="clientCredentialsTokenEndpointFilter"
class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<beans:constructor-arg value="/api/oauth/token" />
<beans:property name="authenticationManager" ref="clientAuthenticationManager" />
</beans:bean>
<beans:bean id="accessDecisionManager"
class="org.springframework.security.access.vote.UnanimousBased">
<beans:constructor-arg>
<beans:list>
<beans:bean
class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
<beans:bean class="org.springframework.security.access.vote.RoleVoter" />
<beans:bean
class="org.springframework.security.access.vote.AuthenticatedVoter" />
<beans:bean
class="org.springframework.security.web.access.expression.WebExpressionVoter" />
</beans:list>
</beans:constructor-arg>
</beans:bean>
<authentication-manager id="clientAuthenticationManager">
<authentication-provider user-service-ref="clientDetailsUserService" />
</authentication-manager>
<authentication-manager alias="authenticationManager"
erase-credentials="false">
<authentication-provider ref="customAuthenticationProvider" />
</authentication-manager>
<beans:bean id="clientDetailsUserService"
class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<beans:constructor-arg ref="clientDetailsService" />
</beans:bean>
<beans:bean id="passwordEncoder"
class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
<beans:constructor-arg value="256" />
</beans:bean>
<beans:bean id="saltSource"
class="org.springframework.security.authentication.dao.ReflectionSaltSource">
<beans:property name="userPropertyToUse" value="id" />
</beans:bean>
<beans:bean id="customAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="userDetailsService" />
<beans:property name="passwordEncoder" ref="passwordEncoder" />
<beans:property name="saltSource" ref="saltSource" />
</beans:bean>
<beans:bean id="clientDetailsService"
class="org.springframework.security.oauth2.provider.client.JdbcClientDetailsService">
<beans:constructor-arg ref="routingDataSource" />
</beans:bean>
<beans:bean id="tokenStore"
class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
<beans:constructor-arg ref="routingDataSource" />
</beans:bean>
<beans:bean id="tokenServices"
class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<beans:property name="tokenStore" ref="tokenStore" />
<beans:property name="clientDetailsService" ref="clientDetailsService" />
<beans:property name="supportRefreshToken" value="true" />
<beans:property name="refreshTokenValiditySeconds"
value="86400" />
<beans:property name="accessTokenValiditySeconds"
value="3600" />
</beans:bean>
<beans:bean id="userApprovalHandler"
class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">
<beans:property name="tokenStore" ref="tokenStore" />
<beans:property name="clientDetailsService" ref="clientDetailsService" />
<beans:property name="requestFactory" ref="oAuth2RequestFactory" />
</beans:bean>
<beans:bean id="oAuth2RequestFactory"
class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory">
<beans:constructor-arg ref="clientDetailsService" />
</beans:bean>
<oauth:authorization-server
client-details-service-ref="clientDetailsService" token-services-ref="tokenServices"
user-approval-handler-ref="userApprovalHandler" token-endpoint-url="/api/oauth/token"
authorization-endpoint-url="/api/oauth/authorize">
<oauth:refresh-token />
<oauth:password />
</oauth:authorization-server>
<oauth:resource-server id="resourceServerFilter"
token-services-ref="tokenServices" />
</beans:beans>
</beans:beans>
<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="passwordEncoder"
class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
<beans:constructor-arg value="256" />
</beans:bean>
<beans:bean id="saltSource"
class="org.springframework.security.authentication.dao.ReflectionSaltSource">
<beans:property name="userPropertyToUse" value="id" />
</beans:bean>
<beans:bean id="customAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="userDetailsService" />
<beans:property name="passwordEncoder" ref="passwordEncoder" />
<beans:property name="saltSource" ref="saltSource" />
</beans:bean>
<authentication-manager alias="authenticationManager"
erase-credentials="false">
<authentication-provider ref="customAuthenticationProvider" />
</authentication-manager>
</beans:beans>
@@ -0,0 +1,7 @@
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#
spring.profiles.default=basicauth
@@ -0,0 +1,43 @@
CREATE TABLE `oauth_client_details` (
`client_id` varchar(128) NOT NULL,
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`authorized_grant_types` varchar(256) DEFAULT NULL,
`web_server_redirect_uri` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` BIT(1) NULL DEFAULT NULL,
PRIMARY KEY (`client_id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;
INSERT INTO `oauth_client_details` (`client_id`, `client_secret`, `scope`, `authorized_grant_types`) VALUES ('community-app', '123', 'all', 'password,refresh_token');
CREATE TABLE `oauth_access_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication_id` varchar(256) DEFAULT NULL,
`user_name` varchar(256) DEFAULT NULL,
`client_id` varchar(256) DEFAULT NULL,
`authentication` blob,
`refresh_token` varchar(256) DEFAULT NULL
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication` blob
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
@@ -14,12 +14,15 @@ import org.mifosplatform.infrastructure.configuration.spring.TestsWithoutDatabas
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration @WebAppConfiguration
@ActiveProfiles("basicauth")
@ContextConfiguration(classes = TestsWithoutDatabaseAndNoJobsConfiguration.class) @ContextConfiguration(classes = TestsWithoutDatabaseAndNoJobsConfiguration.class)
public class CommandHandlerProviderTest { public class CommandHandlerProviderTest {
@@ -7,6 +7,7 @@ package org.mifosplatform.infrastructure.configuration.spring;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
@@ -25,6 +26,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration @WebAppConfiguration
@ActiveProfiles("basicauth")
@ContextConfiguration(classes = TestsWithoutDatabaseAndNoJobsConfiguration.class) @ContextConfiguration(classes = TestsWithoutDatabaseAndNoJobsConfiguration.class)
public class SpringConfigurationTest { public class SpringConfigurationTest {
@@ -9,12 +9,14 @@ import org.junit.runner.RunWith;
import org.mifosplatform.ServerWithMariaDB4jApplication; import org.mifosplatform.ServerWithMariaDB4jApplication;
import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ServerWithMariaDB4jApplication.Configuration.class) @SpringApplicationConfiguration(classes = ServerWithMariaDB4jApplication.Configuration.class)
@WebAppConfiguration @WebAppConfiguration
@ActiveProfiles("basicauth")
@IntegrationTest({ "server.port=0", "management.port=0", "mariaDB4j.port=0", "mariaDB4j.dataDir=null" }) @IntegrationTest({ "server.port=0", "management.port=0", "mariaDB4j.port=0", "mariaDB4j.dataDir=null" })
public abstract class AbstractSpringBootWithMariaDB4jIntegrationTest { public abstract class AbstractSpringBootWithMariaDB4jIntegrationTest {