Allowing foreign applications to access specific endpoints.
diff --git a/component-test/src/main/java/TestSystemTokenOnSpringEndpoints.java b/component-test/src/main/java/TestSystemToken.java
similarity index 63%
rename from component-test/src/main/java/TestSystemTokenOnSpringEndpoints.java
rename to component-test/src/main/java/TestSystemToken.java
index 07cdc5c..66d2f5b 100644
--- a/component-test/src/main/java/TestSystemTokenOnSpringEndpoints.java
+++ b/component-test/src/main/java/TestSystemToken.java
@@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import io.mifos.anubis.example.simple.Example;
import io.mifos.anubis.example.simple.ExampleConfiguration;
import io.mifos.anubis.example.simple.Metrics;
import io.mifos.anubis.example.simple.MetricsFeignClient;
+import io.mifos.anubis.test.v1.SystemSecurityEnvironment;
import io.mifos.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule;
import io.mifos.core.api.context.AutoUserContext;
+import io.mifos.core.api.util.NotFoundException;
import io.mifos.core.test.env.TestEnvironment;
import io.mifos.core.test.fixture.TenantDataStoreContextTestRule;
import io.mifos.core.test.fixture.cassandra.CassandraInitializer;
@@ -44,7 +47,7 @@
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
-public class TestSystemTokenOnSpringEndpoints {
+public class TestSystemToken {
private static final String APP_NAME = "anubis-v1";
@Configuration
@@ -78,14 +81,40 @@
@SuppressWarnings({"SpringAutowiredFieldsWarningInspection", "SpringJavaAutowiringInspection", "SpringJavaAutowiredMembersInspection"})
@Autowired
- protected MetricsFeignClient example;
+ protected MetricsFeignClient metricsFeignClient;
+
+ @SuppressWarnings({"SpringAutowiredFieldsWarningInspection", "SpringJavaAutowiredMembersInspection"})
+ @Autowired
+ Example example;
@Test
- public void shouldBeAbleToGetMetrics() throws Exception {
+ public void shouldBeAbleToGetContactSpringEndpoint() throws Exception {
try (final AutoUserContext ignored = tenantApplicationSecurityEnvironment.createAutoSeshatContext()) {
- final Metrics metrics = example.getMetrics();
+ final Metrics metrics = metricsFeignClient.getMetrics();
Assert.assertTrue(metrics.getThreads() > 0);
}
}
+
+ @Test
+ public void shouldBeAbleToGetForForeignApplication() throws Exception {
+ final TenantApplicationSecurityEnvironmentTestRule tenantForeignApplicationSecurityEnvironment
+ = new TenantApplicationSecurityEnvironmentTestRule("foreign-v1", testEnvironment.serverURI(),
+ new SystemSecurityEnvironment(testEnvironment.getSystemKeyTimestamp(), testEnvironment.getSystemPublicKey(), testEnvironment.getSystemPrivateKey()));
+ try (final AutoUserContext ignored = tenantForeignApplicationSecurityEnvironment.createAutoSeshatContext()) {
+ final boolean ret = example.forApplication("foreign-v1");
+ Assert.assertTrue(ret);
+ }
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void shouldNotBeAbleToGetForForeignApplicationWhenForeignApplicationNotEnabled() throws Exception {
+ final TenantApplicationSecurityEnvironmentTestRule tenantForeignApplicationSecurityEnvironment
+ = new TenantApplicationSecurityEnvironmentTestRule("foreign-v1", testEnvironment.serverURI(),
+ new SystemSecurityEnvironment(testEnvironment.getSystemKeyTimestamp(), testEnvironment.getSystemPublicKey(), testEnvironment.getSystemPrivateKey()));
+ try (final AutoUserContext ignored = tenantForeignApplicationSecurityEnvironment.createAutoSeshatContext()) {
+ example.notForApplication("foreign-v1");
+ Assert.fail("Shouldn't be able to access for a foreign token in this case.");
+ }
+ }
}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/anubis/example/simple/Example.java b/component-test/src/main/java/io/mifos/anubis/example/simple/Example.java
index 4a3f29c..db8c503 100644
--- a/component-test/src/main/java/io/mifos/anubis/example/simple/Example.java
+++ b/component-test/src/main/java/io/mifos/anubis/example/simple/Example.java
@@ -17,6 +17,7 @@
import io.mifos.core.api.util.CustomFeignClientsConfiguration;
import org.springframework.cloud.netflix.feign.FeignClient;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -33,4 +34,10 @@
@RequestMapping(value = "foo", method = RequestMethod.GET)
boolean foo();
+
+ @RequestMapping(value = "{applicationidentifier}/forapplication", method = RequestMethod.GET)
+ boolean forApplication(@PathVariable("applicationidentifier") final String applicationIdentifier);
+
+ @RequestMapping(value = "{applicationidentifier}/notforapplication", method = RequestMethod.GET)
+ boolean notForApplication(@PathVariable("applicationidentifier") final String applicationIdentifier);
}
diff --git a/component-test/src/main/java/io/mifos/anubis/example/simple/ExampleRestController.java b/component-test/src/main/java/io/mifos/anubis/example/simple/ExampleRestController.java
index d988f27..a05fb41 100644
--- a/component-test/src/main/java/io/mifos/anubis/example/simple/ExampleRestController.java
+++ b/component-test/src/main/java/io/mifos/anubis/example/simple/ExampleRestController.java
@@ -19,6 +19,7 @@
import io.mifos.anubis.annotation.Permittable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@@ -60,4 +61,17 @@
public ResponseEntity<Boolean> foo() {
return ResponseEntity.ok(false);
}
+
+ @RequestMapping(value = "/{applicationidentifier}/forapplication", method = RequestMethod.GET)
+ @Permittable(value = AcceptedTokenType.SYSTEM, permittedEndpoint = "/{applicationidentifier}/forapplication", acceptTokenIntendedForForeignApplication = true)
+ public ResponseEntity<Boolean> forApplication(@PathVariable("applicationidentifier") final String applicationIdentifier) {
+ return ResponseEntity.ok(true);
+ }
+
+ @SuppressWarnings("DefaultAnnotationParam")
+ @RequestMapping(value = "/{applicationidentifier}/notforapplication", method = RequestMethod.GET)
+ @Permittable(value = AcceptedTokenType.SYSTEM, permittedEndpoint = "/{applicationidentifier}/forapplication", acceptTokenIntendedForForeignApplication = false)
+ public ResponseEntity<Boolean> notForApplication(@PathVariable("applicationidentifier") final String applicationIdentifier) {
+ return ResponseEntity.ok(true);
+ }
}
diff --git a/library/src/main/java/io/mifos/anubis/config/AnubisSecurityConfigurerAdapter.java b/library/src/main/java/io/mifos/anubis/config/AnubisSecurityConfigurerAdapter.java
index 541c0fc..2553dbb 100644
--- a/library/src/main/java/io/mifos/anubis/config/AnubisSecurityConfigurerAdapter.java
+++ b/library/src/main/java/io/mifos/anubis/config/AnubisSecurityConfigurerAdapter.java
@@ -20,6 +20,7 @@
import io.mifos.anubis.security.ApplicationPermission;
import io.mifos.anubis.security.IsisAuthenticatedAuthenticationProvider;
import io.mifos.anubis.security.UrlPermissionChecker;
+import io.mifos.core.lang.ApplicationName;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
@@ -54,9 +55,12 @@
@EnableWebSecurity
public class AnubisSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
final private Logger logger;
+ final private ApplicationName applicationName;
- public AnubisSecurityConfigurerAdapter(final @Qualifier(LOGGER_NAME) Logger logger) {
+ public AnubisSecurityConfigurerAdapter(final @Qualifier(LOGGER_NAME) Logger logger,
+ final ApplicationName applicationName) {
this.logger = logger;
+ this.applicationName = applicationName;
}
@PostConstruct
@@ -92,7 +96,7 @@
private AccessDecisionManager defaultAccessDecisionManager() {
final List<AccessDecisionVoter<?>> voters = new ArrayList<>();
- voters.add(new UrlPermissionChecker(logger));
+ voters.add(new UrlPermissionChecker(logger, applicationName));
return new UnanimousBased(voters);
}
diff --git a/library/src/main/java/io/mifos/anubis/security/AnubisAuthentication.java b/library/src/main/java/io/mifos/anubis/security/AnubisAuthentication.java
index add7eb6..ab7385f 100644
--- a/library/src/main/java/io/mifos/anubis/security/AnubisAuthentication.java
+++ b/library/src/main/java/io/mifos/anubis/security/AnubisAuthentication.java
@@ -32,16 +32,16 @@
private final String token;
private final String userIdentifier;
- private final String callingApplicationIdentifier;
+ private final String forApplicationName;
private final Set<ApplicationPermission> applicationPermissions;
- AnubisAuthentication(final String token, final String userIdentifier, final String callingApplicationIdentifier,
+ AnubisAuthentication(final String token, final String userIdentifier, final String forApplicationName,
final Set<ApplicationPermission> applicationPermissions) {
authenticated = true;
this.token = token;
this.userIdentifier = userIdentifier;
- this.callingApplicationIdentifier = callingApplicationIdentifier;
+ this.forApplicationName = forApplicationName;
this.applicationPermissions = Collections.unmodifiableSet(new HashSet<>(applicationPermissions));
}
@@ -62,7 +62,7 @@
@Override
public AnubisPrincipal getPrincipal() {
- return new AnubisPrincipal(userIdentifier, callingApplicationIdentifier);
+ return new AnubisPrincipal(userIdentifier, forApplicationName);
}
@Override
diff --git a/library/src/main/java/io/mifos/anubis/security/ApplicationPermission.java b/library/src/main/java/io/mifos/anubis/security/ApplicationPermission.java
index 147d233..ee3e2d9 100644
--- a/library/src/main/java/io/mifos/anubis/security/ApplicationPermission.java
+++ b/library/src/main/java/io/mifos/anubis/security/ApplicationPermission.java
@@ -18,6 +18,7 @@
import io.mifos.anubis.api.v1.domain.AllowedOperation;
import io.mifos.anubis.service.PermissionSegmentMatcher;
import io.mifos.core.api.util.ApiConstants;
+import io.mifos.core.lang.ApplicationName;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.FilterInvocation;
@@ -60,11 +61,17 @@
return URL_AUTHORITY;
}
- boolean matches(final FilterInvocation filterInvocation, final AnubisPrincipal principal) {
- return matches(filterInvocation.getRequest(), principal);
+ boolean matches(final FilterInvocation filterInvocation,
+ final ApplicationName applicationName,
+ final AnubisPrincipal principal) {
+ return matches(filterInvocation.getRequest(), applicationName, principal);
}
- boolean matches(final HttpServletRequest request, final AnubisPrincipal principal) {
+ boolean matches(final HttpServletRequest request,
+ final ApplicationName applicationName,
+ final AnubisPrincipal principal) {
+ if (!acceptTokenIntendedForForeignApplication && !applicationName.toString().equals(principal.getForApplicationName()))
+ return false;
boolean isSu = principal.getUser().equals(ApiConstants.SYSTEM_SU);
return matchesHelper(
request.getServletPath(),
diff --git a/library/src/main/java/io/mifos/anubis/security/GuestAuthenticator.java b/library/src/main/java/io/mifos/anubis/security/GuestAuthenticator.java
index a5301b9..12b2ce4 100644
--- a/library/src/main/java/io/mifos/anubis/security/GuestAuthenticator.java
+++ b/library/src/main/java/io/mifos/anubis/security/GuestAuthenticator.java
@@ -18,6 +18,7 @@
import io.mifos.anubis.annotation.AcceptedTokenType;
import io.mifos.anubis.api.v1.RoleConstants;
import io.mifos.anubis.service.PermittableService;
+import io.mifos.core.lang.ApplicationName;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -34,12 +35,15 @@
public class GuestAuthenticator {
private Set<ApplicationPermission> permissions;
private final Logger logger;
+ private final ApplicationName applicationName;
@Autowired
public GuestAuthenticator(final PermittableService permittableService,
- final @Qualifier(LOGGER_NAME) Logger logger) {
+ final @Qualifier(LOGGER_NAME) Logger logger,
+ final ApplicationName applicationName) {
this.permissions = permittableService.getPermittableEndpointsAsPermissions(AcceptedTokenType.GUEST);
this.logger = logger;
+ this.applicationName = applicationName;
}
AnubisAuthentication authenticate(final String user) {
@@ -48,6 +52,6 @@
logger.info("Guest access \"authenticated\" successfully.", user);
- return new AnubisAuthentication(null, RoleConstants.GUEST_USER_IDENTIFIER, null, permissions);
+ return new AnubisAuthentication(null, RoleConstants.GUEST_USER_IDENTIFIER, applicationName.toString(), permissions);
}
}
diff --git a/library/src/main/java/io/mifos/anubis/security/SystemAuthenticator.java b/library/src/main/java/io/mifos/anubis/security/SystemAuthenticator.java
index 88651e7..46c01e3 100644
--- a/library/src/main/java/io/mifos/anubis/security/SystemAuthenticator.java
+++ b/library/src/main/java/io/mifos/anubis/security/SystemAuthenticator.java
@@ -23,7 +23,6 @@
import io.mifos.anubis.service.PermittableService;
import io.mifos.anubis.token.TokenType;
import io.mifos.core.api.util.ApiConstants;
-import io.mifos.core.lang.ApplicationName;
import io.mifos.core.lang.TenantContextHolder;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
@@ -40,18 +39,15 @@
@Component
public class SystemAuthenticator {
private final SystemRsaKeyProvider systemRsaKeyProvider;
- private final ApplicationName applicationName;
private final Set<ApplicationPermission> permissions;
private final Logger logger;
@Autowired
public SystemAuthenticator(
final SystemRsaKeyProvider systemRsaKeyProvider,
- final ApplicationName applicationName,
final PermittableService permittableService,
final @Qualifier(LOGGER_NAME) Logger logger) {
this.systemRsaKeyProvider = systemRsaKeyProvider;
- this.applicationName = applicationName;
this.permissions = permittableService.getPermittableEndpointsAsPermissions(AcceptedTokenType.SYSTEM);
this.logger = logger;
}
@@ -75,9 +71,8 @@
//noinspection unchecked
final Jwt<Header, Claims> result = jwtParser.parse(token);
if (result.getBody() == null ||
- result.getBody().getAudience() == null ||
- !result.getBody().getAudience().equals(applicationName.toString())) {
- logger.info("System token for user {}, with key timestamp {} failed to authenticate. Audience was set wrong or was not set.", user, keyTimestamp);
+ result.getBody().getAudience() == null) {
+ logger.info("System token for user {}, with key timestamp {} failed to authenticate. Audience was not set.", user, keyTimestamp);
throw AmitAuthenticationException.invalidToken();
}
diff --git a/library/src/main/java/io/mifos/anubis/security/TenantAuthenticator.java b/library/src/main/java/io/mifos/anubis/security/TenantAuthenticator.java
index 1ec5a7b..bd53f33 100644
--- a/library/src/main/java/io/mifos/anubis/security/TenantAuthenticator.java
+++ b/library/src/main/java/io/mifos/anubis/security/TenantAuthenticator.java
@@ -88,7 +88,7 @@
logger.info("Tenant token for user {}, with key timestamp {} authenticated successfully.", user, keyTimestamp);
return new AnubisAuthentication(TokenConstants.PREFIX + token,
- jwt.getBody().getSubject(), null, permissions
+ jwt.getBody().getSubject(), applicationNameWithVersion, permissions
);
}
catch (final JwtException e) {
diff --git a/library/src/main/java/io/mifos/anubis/security/UrlPermissionChecker.java b/library/src/main/java/io/mifos/anubis/security/UrlPermissionChecker.java
index 22c185d..2c55cd1 100644
--- a/library/src/main/java/io/mifos/anubis/security/UrlPermissionChecker.java
+++ b/library/src/main/java/io/mifos/anubis/security/UrlPermissionChecker.java
@@ -15,6 +15,7 @@
*/
package io.mifos.anubis.security;
+import io.mifos.core.lang.ApplicationName;
import org.slf4j.Logger;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
@@ -30,9 +31,11 @@
*/
public class UrlPermissionChecker implements AccessDecisionVoter<FilterInvocation> {
private final Logger logger;
+ private final ApplicationName applicationName;
- public UrlPermissionChecker(final Logger logger) {
+ public UrlPermissionChecker(final Logger logger, final ApplicationName applicationName) {
this.logger = logger;
+ this.applicationName = applicationName;
}
@Override public boolean supports(final ConfigAttribute attribute) {
@@ -58,7 +61,7 @@
final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
final Optional<ApplicationPermission> matchedPermission = authorities.stream()
.map(x -> (ApplicationPermission) x)
- .filter(x -> x.matches(filterInvocation, authentication.getPrincipal()))
+ .filter(x -> x.matches(filterInvocation, applicationName, authentication.getPrincipal()))
.findAny();
matchedPermission.ifPresent(x -> logger.debug("Authorizing access to {} based on permission: {}"
diff --git a/library/src/test/java/io/mifos/anubis/security/ApplicationPermissionTest.java b/library/src/test/java/io/mifos/anubis/security/ApplicationPermissionTest.java
index 19e7452..0e1c3dc 100644
--- a/library/src/test/java/io/mifos/anubis/security/ApplicationPermissionTest.java
+++ b/library/src/test/java/io/mifos/anubis/security/ApplicationPermissionTest.java
@@ -17,6 +17,7 @@
import io.mifos.anubis.api.v1.domain.AllowedOperation;
import io.mifos.core.api.util.ApiConstants;
+import io.mifos.core.lang.ApplicationName;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,11 +42,11 @@
private String permittedPath = "/heart";
private AllowedOperation allowedOperation = AllowedOperation.READ;
private boolean acceptTokenIntendedForForeignApplication = false;
- private String calledApplication = "grainCounter";
+ private String calledApplication = "graincounter-v1";
private String requestedPath = "/heart";
private String requestedOperation = "GET";
private String user = "Nebamun";
- private String forApplication = "grainCounter";
+ private String forApplication = "graincounter-v1";
private boolean expectedResult = true;
private TestCase(final String caseName) {
@@ -79,8 +80,8 @@
return this;
}
- String getCalledApplication() {
- return calledApplication;
+ ApplicationName getCalledApplication() {
+ return ApplicationName.fromSpringApplicationName(calledApplication);
}
TestCase calledApplication(final String newVal)
@@ -187,24 +188,31 @@
.permittedPath("/x/y/z/*").requestedPath("/m/n/o/")
.expectedResult(false));
ret.add(new TestCase("{applicationidentifier} but permission doesn't allow foreign forApplication")
- .permittedPath("/m/{applicationidentifier}/o").requestedPath("/m/b/o/")
+ .permittedPath("/m/{applicationidentifier}/o").requestedPath("/m/bcde-v1/o/")
.acceptTokenIntendedForForeignApplication(false)
- .calledApplication("a").forApplication("b")
+ .calledApplication("abcd-v1").forApplication("bcde-v1")
.expectedResult(false));
ret.add(new TestCase("{applicationidentifier} and permission does allow foreign forApplication")
- .permittedPath("/m/{applicationidentifier}/o").requestedPath("/m/b/o/")
+ .permittedPath("/m/{applicationidentifier}/o").requestedPath("/m/bcde-v1/o/")
.acceptTokenIntendedForForeignApplication(true)
- .calledApplication("a").forApplication("b")
+ .calledApplication("abcd-v1").forApplication("bcde-v1")
.expectedResult(true));
ret.add(new TestCase("No {applicationidentifier} even though permission does allow foreign forApplication")
- .permittedPath("/m/n/o").requestedPath("/m/b/o/")
+ .permittedPath("/m/n/o").requestedPath("/m/bcde-v1/o/")
.acceptTokenIntendedForForeignApplication(true)
- .calledApplication("a").forApplication("b")
+ .calledApplication("abcd-v1").forApplication("bcde-v1")
.expectedResult(false));
ret.add(new TestCase("{applicationidentifier} and permission does allow foreign forApplication, but application isn't foreign.")
- .permittedPath("/m/{applicationidentifier}/o").requestedPath("/m/a/o/")
+ .permittedPath("/m/{applicationidentifier}/o").requestedPath("/m/abcd-v1/o/")
.acceptTokenIntendedForForeignApplication(true)
- .calledApplication("a").forApplication("a")
+ .calledApplication("abcd-v1").forApplication("abcd-v1")
+ .expectedResult(true));
+ ret.add(new TestCase("initialize")
+ .permittedPath("/initialize").requestedPath("/initialize")
+ .acceptTokenIntendedForForeignApplication(false)
+ .calledApplication("abcd-v1").forApplication("abcd-v1")
+ .allowedOperation(AllowedOperation.CHANGE)
+ .requestedOperation("POST")
.expectedResult(true));
return ret;
@@ -218,7 +226,7 @@
when(requestMock.getServletPath()).thenReturn(testCase.getRequestedPath());
when(requestMock.getMethod()).thenReturn(testCase.getRequestedOperation());
- final boolean matches = testSubject.matches(requestMock, testCase.getPrincipal());
+ final boolean matches = testSubject.matches(requestMock, testCase.getCalledApplication(), testCase.getPrincipal());
Assert.assertEquals("Testcase gave wrong result: '" + testCase.toString() + "'",
testCase.getExpectedResult(), matches);
diff --git a/test/src/main/java/io/mifos/anubis/test/v1/SystemSecurityEnvironment.java b/test/src/main/java/io/mifos/anubis/test/v1/SystemSecurityEnvironment.java
index b08e459..faed373 100644
--- a/test/src/main/java/io/mifos/anubis/test/v1/SystemSecurityEnvironment.java
+++ b/test/src/main/java/io/mifos/anubis/test/v1/SystemSecurityEnvironment.java
@@ -34,7 +34,6 @@
import io.mifos.core.api.context.AutoUserContext;
import io.mifos.core.api.util.ApiConstants;
import io.mifos.core.api.util.UserContextHolder;
-import io.mifos.core.lang.ApplicationName;
import io.mifos.core.lang.TenantContextHolder;
import io.mifos.core.lang.security.RsaKeyPairFactory;
import org.mockito.Mockito;
@@ -204,7 +203,6 @@
final SystemAuthenticator systemAuthenticator = new SystemAuthenticator(
systemRsaKeyProvider,
- ApplicationName.appNameWithVersion(forService, forServiceVersion),
permittableService,
logger);
try {