Skip to content
Permalink
Browse files
Merge pull request #9 from myrle-krantz/develop
Added the ability to specify that an endpoint can be called with a system token intended for a different application in specialized circumstances.
  • Loading branch information
myrle-krantz committed May 29, 2017
2 parents 5f6e3bb + 1b46e56 commit d11032de2647cd589a6ebd1190188dc0c2a32aef
Showing 19 changed files with 294 additions and 52 deletions.
@@ -21,6 +21,9 @@
import java.util.HashSet;
import java.util.Set;

/**
* @author Myrle Krantz
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public enum AllowedOperation {
@SerializedName("READ")
@@ -36,6 +36,8 @@ public class PermittableEndpoint {
@Nullable
private String groupId;

private boolean acceptTokenIntendedForForeignApplication;

public PermittableEndpoint() {
super();
}
@@ -52,6 +54,13 @@ public PermittableEndpoint(final String path, final String method, final String
this.groupId = groupId;
}

public PermittableEndpoint(String path, String method, String groupId, boolean acceptTokenIntendedForForeignApplication) {
this.path = path;
this.method = method;
this.groupId = groupId;
this.acceptTokenIntendedForForeignApplication = acceptTokenIntendedForForeignApplication;
}

public String getPath() {
return path;
}
@@ -76,19 +85,28 @@ public void setGroupId(String groupId) {
this.groupId = groupId;
}

public boolean isAcceptTokenIntendedForForeignApplication() {
return acceptTokenIntendedForForeignApplication;
}

public void setAcceptTokenIntendedForForeignApplication(boolean acceptTokenIntendedForForeignApplication) {
this.acceptTokenIntendedForForeignApplication = acceptTokenIntendedForForeignApplication;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PermittableEndpoint that = (PermittableEndpoint) o;
return Objects.equals(path, that.path) &&
return acceptTokenIntendedForForeignApplication == that.acceptTokenIntendedForForeignApplication &&
Objects.equals(path, that.path) &&
Objects.equals(method, that.method) &&
Objects.equals(groupId, that.groupId);
}

@Override
public int hashCode() {
return Objects.hash(path, method, groupId);
return Objects.hash(path, method, groupId, acceptTokenIntendedForForeignApplication);
}

@Override
@@ -97,6 +115,7 @@ public String toString() {
"path='" + path + '\'' +
", method='" + method + '\'' +
", groupId='" + groupId + '\'' +
", acceptTokenIntendedForForeignApplication=" + acceptTokenIntendedForForeignApplication +
'}';
}
}
@@ -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);
}
}
@@ -28,4 +28,5 @@
AcceptedTokenType value() default AcceptedTokenType.TENANT;
String groupId() default "";
String permittedEndpoint() default "";
boolean acceptTokenIntendedForForeignApplication() default false;
}
@@ -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);
}

@@ -38,11 +38,11 @@ protected void doFilterInternal(

final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

Object principal = authentication.getPrincipal();
Object credentials = authentication.getCredentials();
final String principalName = authentication.getName();
final Object credentials = authentication.getCredentials();

if (principal != null && credentials != null) {
UserContextHolder.setAccessToken(principal.toString(), credentials.toString());
if (principalName != null && credentials != null) {
UserContextHolder.setAccessToken(principalName, credentials.toString());
}

filterChain.doFilter(request, response);
@@ -32,14 +32,16 @@ class AnubisAuthentication implements Authentication {

private final String token;
private final String userIdentifier;
private final String forApplicationName;
private final Set<ApplicationPermission> applicationPermissions;

AnubisAuthentication(final String token, final String userIdentifier,
AnubisAuthentication(final String token, final String userIdentifier, final String forApplicationName,
final Set<ApplicationPermission> applicationPermissions) {
authenticated = true;

this.token = token;
this.userIdentifier = userIdentifier;
this.forApplicationName = forApplicationName;
this.applicationPermissions = Collections.unmodifiableSet(new HashSet<>(applicationPermissions));
}

@@ -59,8 +61,8 @@ public String getDetails() {
}

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

@Override
@@ -0,0 +1,62 @@
/*
* Copyright 2017 The Mifos Initiative.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.mifos.anubis.security;

import java.util.Objects;

/**
* @author Myrle Krantz
*/
@SuppressWarnings("unused")
public class AnubisPrincipal {
private final String user;
private final String forApplicationName;

AnubisPrincipal(String user, String forApplicationName) {
this.user = user;
this.forApplicationName = forApplicationName;
}

public String getUser() {
return user;
}

public String getForApplicationName() {
return forApplicationName;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AnubisPrincipal that = (AnubisPrincipal) o;
return Objects.equals(user, that.user) &&
Objects.equals(forApplicationName, that.forApplicationName);
}

@Override
public int hashCode() {
return Objects.hash(user, forApplicationName);
}

@Override
public String toString() {
return "AnubisPrincipal{" +
"user='" + user + '\'' +
", forApplicationName='" + forApplicationName + '\'' +
'}';
}
}
@@ -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;

@@ -40,11 +41,15 @@ public class ApplicationPermission implements GrantedAuthority {

private final AllowedOperation allowedOperation;

private final boolean acceptTokenIntendedForForeignApplication;

public ApplicationPermission(
final String servletPath,
final AllowedOperation allowedOperation) {
final AllowedOperation allowedOperation,
final boolean acceptTokenIntendedForForeignApplication) {
this.allowedOperation = allowedOperation;
servletPathSegmentMatchers = PermissionSegmentMatcher.getServletPathSegmentMatchers(servletPath);
this.acceptTokenIntendedForForeignApplication = acceptTokenIntendedForForeignApplication;
}


@@ -56,16 +61,22 @@ AllowedOperation getAllowedOperation() {
return URL_AUTHORITY;
}

boolean matches(final FilterInvocation filterInvocation, final String 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 String principal) {
boolean isSu = principal.equals(ApiConstants.SYSTEM_SU);
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(),
request.getMethod(),
(matcher, segment) -> matcher.matches(segment, principal, isSu));
(matcher, segment) -> matcher.matches(segment, principal, acceptTokenIntendedForForeignApplication, isSu));
}

private boolean matchesHelper(final String servletPath, final String method,

0 comments on commit d11032d

Please sign in to comment.