Skip to content
Permalink
Browse files
Allowing foreign applications to access specific endpoints.
  • Loading branch information
mifosio-04-04-2018 committed May 29, 2017
1 parent 7092a7d commit 4f85a8e19464ee466ee1a10663566d643a50d0bd
Showing 12 changed files with 109 additions and 40 deletions.
@@ -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 @@ public Logger logger() {

@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.");
}
}
}
@@ -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 @@ public interface Example {

@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);
}
@@ -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<Void> uninitialize()
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);
}
}
@@ -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 @@ public FilterRegistrationBean userContextFilter()

private AccessDecisionManager defaultAccessDecisionManager() {
final List<AccessDecisionVoter<?>> voters = new ArrayList<>();
voters.add(new UrlPermissionChecker(logger));
voters.add(new UrlPermissionChecker(logger, applicationName));
return new UnanimousBased(voters);
}

@@ -32,16 +32,16 @@ class AnubisAuthentication implements Authentication {

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 @@ public String getDetails() {

@Override
public AnubisPrincipal getPrincipal() {
return new AnubisPrincipal(userIdentifier, callingApplicationIdentifier);
return new AnubisPrincipal(userIdentifier, forApplicationName);
}

@Override
@@ -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 @@ AllowedOperation getAllowedOperation() {
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(),
@@ -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 @@ AnubisAuthentication authenticate(final String user) {

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);
}
}
@@ -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 @@ public AnubisAuthentication authenticate(
//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();
}

@@ -88,7 +88,7 @@ AnubisAuthentication authenticate(
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) {
@@ -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 @@ public UrlPermissionChecker(final Logger logger) {
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: {}"
@@ -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 @@ public class ApplicationPermissionTest {
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 @@ TestCase acceptTokenIntendedForForeignApplication(boolean newVal) {
return this;
}

String getCalledApplication() {
return calledApplication;
ApplicationName getCalledApplication() {
return ApplicationName.fromSpringApplicationName(calledApplication);
}

TestCase calledApplication(final String newVal)
@@ -187,24 +188,31 @@ public static Collection testCases() {
.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 @@ public static Collection testCases() {
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);

0 comments on commit 4f85a8e

Please sign in to comment.