Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[breaking] Transformed Permissions utility class into a PermissionFac…
…tory component, thus allowing to potentially override hasRole() implementation if needed
  • Loading branch information
fcamblor committed May 11, 2016
1 parent 0cfcc00 commit ba28876
Show file tree
Hide file tree
Showing 14 changed files with 85 additions and 62 deletions.
4 changes: 2 additions & 2 deletions restx-admin/src/main/java/restx/admin/AdminModule.java
Expand Up @@ -73,7 +73,7 @@ public String getName() {
}

@Provides
public RestxFilter adminRoleFilter() {
public RestxFilter adminRoleFilter(final PermissionFactory permissionFactory) {
return new RestxFilter() {
final Pattern privatePath = Pattern.compile("^/@/(?!(ui|webjars)/).*$");

Expand All @@ -87,7 +87,7 @@ public Optional<RestxHandlerMatch> match(RestxRequest req) {
public void handle(RestxRequestMatch match, RestxRequest req, RestxResponse resp, RestxContext ctx) throws IOException {
final RestxSession current = RestxSession.current();
if (current.getPrincipal().isPresent() &&
Permissions.hasRole(RESTX_ADMIN_ROLE).has(current.getPrincipal().get(), Collections.<String, String>emptyMap()).isPresent()) {
permissionFactory.hasRole(RESTX_ADMIN_ROLE).has(current.getPrincipal().get(), Collections.<String, String>emptyMap()).isPresent()) {
ctx.nextHandlerMatch().handle(req, resp, ctx);
} else {
throw new WebException(HttpStatus.UNAUTHORIZED);
Expand Down
@@ -1,8 +1,5 @@
package restx.exceptions;

import static restx.security.Permissions.hasRole;


import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableCollection;
Expand All @@ -15,6 +12,7 @@
import restx.factory.Component;
import restx.jackson.FrontObjectMapperFactory;
import restx.jackson.StdJsonProducerEntityRoute;
import restx.security.PermissionFactory;
import restx.security.RestxSecurityManager;

import javax.inject.Named;
Expand All @@ -31,12 +29,15 @@ public class ErrorDescriptorsRoute extends StdJsonProducerEntityRoute {

private final ImmutableMap<String, ErrorDescriptor> errorDescriptors;
private final RestxSecurityManager securityManager;
private PermissionFactory permissionFactory;

public ErrorDescriptorsRoute(Iterable<ErrorDescriptor> errorDescriptors,
@Named(FrontObjectMapperFactory.WRITER_NAME) ObjectWriter objectWriter,
RestxSecurityManager securityManager) {
RestxSecurityManager securityManager,
PermissionFactory permissionFactory) {

super("ErrorDescriptorsRoute", ImmutableCollection.class, objectWriter, new StdRestxRequestMatcher("GET", "/@/errors/descriptors"));
this.permissionFactory = permissionFactory;
Map<String, ErrorDescriptor> map = Maps.newLinkedHashMap();
for (ErrorDescriptor errorDescriptor : errorDescriptors) {
if (map.containsKey(errorDescriptor.getErrorCode())) {
Expand All @@ -50,7 +51,7 @@ public ErrorDescriptorsRoute(Iterable<ErrorDescriptor> errorDescriptors,

@Override
protected Optional<?> doRoute(RestxRequest restxRequest, RestxRequestMatch match, Object i) throws IOException {
securityManager.check(restxRequest, match, hasRole(AdminModule.RESTX_ADMIN_ROLE));
securityManager.check(restxRequest, match, permissionFactory.hasRole(AdminModule.RESTX_ADMIN_ROLE));
return Optional.of(errorDescriptors.values());
}
}
Expand Up @@ -13,6 +13,7 @@
import restx.factory.NamedComponent;
import restx.jackson.FrontObjectMapperFactory;
import restx.jackson.StdJsonProducerEntityRoute;
import restx.security.PermissionFactory;
import restx.security.RestxSecurityManager;

import javax.inject.Inject;
Expand All @@ -21,7 +22,6 @@
import java.util.*;

import static restx.apidocs.ApiDocsIndexRoute.getRouterApiPath;
import static restx.security.Permissions.hasRole;

/**
* Serves the swagger api declaration of one router, which looks like that:
Expand All @@ -45,18 +45,20 @@
public class ApiDeclarationRoute extends StdJsonProducerEntityRoute {
private final Factory factory;
private final RestxSecurityManager securityManager;
private PermissionFactory permissionFactory;

@Inject
public ApiDeclarationRoute(@Named(FrontObjectMapperFactory.WRITER_NAME) ObjectWriter writer,
Factory factory, RestxSecurityManager securityManager) {
Factory factory, RestxSecurityManager securityManager, PermissionFactory permissionFactory) {
super("ApiDeclarationRoute", Map.class, writer, new StdRestxRequestMatcher("GET", "/@/api-docs/{router}"));
this.factory = factory;
this.securityManager = securityManager;
this.permissionFactory = permissionFactory;
}

@Override
protected Optional<?> doRoute(RestxRequest restxRequest, RestxRequestMatch match, Object body) throws IOException {
securityManager.check(restxRequest, match, hasRole(AdminModule.RESTX_ADMIN_ROLE));
securityManager.check(restxRequest, match, permissionFactory.hasRole(AdminModule.RESTX_ADMIN_ROLE));
String routerName = match.getPathParam("router");
Optional<NamedComponent<RestxRouter>> router = getRouterByName(factory, routerName);

Expand Down
11 changes: 6 additions & 5 deletions restx-apidocs/src/main/java/restx/apidocs/ApiDocsIndexRoute.java
@@ -1,8 +1,5 @@
package restx.apidocs;

import static restx.security.Permissions.hasRole;


import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.common.base.CaseFormat;
import com.google.common.base.Optional;
Expand All @@ -15,6 +12,7 @@
import restx.factory.NamedComponent;
import restx.jackson.FrontObjectMapperFactory;
import restx.jackson.StdJsonProducerEntityRoute;
import restx.security.PermissionFactory;
import restx.security.RestxSecurityManager;

import javax.inject.Inject;
Expand Down Expand Up @@ -46,20 +44,23 @@
public class ApiDocsIndexRoute extends StdJsonProducerEntityRoute {
private final Factory factory;
private final RestxSecurityManager securityManager;
private PermissionFactory permissionFactory;

@Inject
public ApiDocsIndexRoute(@Named(FrontObjectMapperFactory.WRITER_NAME) ObjectWriter writer,
Factory factory,
RestxSecurityManager securityManager) {
RestxSecurityManager securityManager,
PermissionFactory permissionFactory) {

super("ApiDocsIndexRoute", Map.class, writer, new StdRestxRequestMatcher("GET", "/@/api-docs"));
this.factory = factory;
this.securityManager = securityManager;
this.permissionFactory = permissionFactory;
}

@Override
protected Optional<?> doRoute(RestxRequest restxRequest, RestxRequestMatch match, Object i) throws IOException {
securityManager.check(restxRequest, match, hasRole(AdminModule.RESTX_ADMIN_ROLE));
securityManager.check(restxRequest, match, permissionFactory.hasRole(AdminModule.RESTX_ADMIN_ROLE));
return Optional.of(ImmutableMap.builder()
.put("apiVersion", "0.1") // TODO
.put("swaggerVersion", "1.1")
Expand Down
Expand Up @@ -112,32 +112,32 @@ private String buildPermission(ResourceMethodAnnotation annotation, TypeElement
String permission;
PermitAll permitAll = annotation.methodElem.getAnnotation(PermitAll.class);
if (permitAll != null) {
permission = "open()";
permission = "pf.open()";
} else {
RolesAllowed rolesAllowed = annotation.methodElem.getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
List<String> roles = new ArrayList<>();
for (String role : rolesAllowed.value()) {
for(String wildcardedRole : generateWildcardRolesFor(role)){
roles.add("hasRole(\"" + wildcardedRole + "\")");
roles.add("pf.hasRole(\"" + wildcardedRole + "\")");
}
}
switch (roles.size()) {
case 0:
permission = "isAuthenticated()";
permission = "pf.isAuthenticated()";
break;
case 1:
permission = roles.get(0);
break;
default:
permission = "anyOf(" + Joiner.on(", ").join(roles) + ")";
permission = "pf.anyOf(" + Joiner.on(", ").join(roles) + ")";
}
} else {
permitAll = typeElem.getAnnotation(PermitAll.class);
if (permitAll != null) {
permission = "open()";
permission = "pf.open()";
} else {
permission = "isAuthenticated()";
permission = "pf.isAuthenticated()";
}
}
}
Expand Down
Expand Up @@ -12,7 +12,7 @@ import restx.entity.*;
import restx.http.*;
import restx.factory.*;
import restx.security.*;
import static restx.security.Permissions.*;
import restx.security.PermissionFactory;
import restx.description.*;
import restx.converters.MainStringConverter;
import static restx.common.MorePreconditions.checkPresent;
Expand All @@ -32,6 +32,7 @@ public class {{router}} extends RestxRouter {
final EntityRequestBodyReaderRegistry readerRegistry,
final EntityResponseWriterRegistry writerRegistry,
final MainStringConverter converter,
final PermissionFactory pf,
final Optional<Validator> validator,
final RestxSecurityManager securityManager) {
super(
Expand Down
@@ -1,6 +1,7 @@
package restx.security;

import com.google.common.base.Optional;
import restx.factory.Component;

import java.util.Arrays;
import java.util.Map;
Expand All @@ -11,7 +12,8 @@
* Provides a set of useful permissions, including the OPEN permission which is the only one that can allow access
* to a resource without being authenticated.
*/
public class Permissions {
@Component
public class PermissionFactory {
private static final Pattern ROLE_PARAM_INTERPOLATOR_REGEX = Pattern.compile("\\{(.+?)\\}");

private static final Permission OPEN = new Permission() {
Expand Down Expand Up @@ -40,23 +42,30 @@ public String toString() {
/**
* This is the only permission that can allow access to a resource without being authenticated.
*/
public static Permission open() {
public Permission open() {
return OPEN;
}

/**
* This is the most basic permission which is true as soon as a principal is authenticated.
*/
public static Permission isAuthenticated() {
public Permission isAuthenticated() {
return IS_AUTHENTICATED;
}

public boolean isOpen(Permission permission) {
return permission == open();
}

public boolean isIsAuthenticated(Permission permission) {
return permission == isAuthenticated();
}

/**
* This permission is true as soon as the principal has the given role
* @param role the role to check
*/
public static Permission hasRole(final String role) {
public Permission hasRole(final String role) {
return new Permission() {
public final String TO_STRING = "HAS_ROLE[" + role + "]";

Expand All @@ -81,7 +90,7 @@ public String toString() {
};
}

protected static String interpolateRole(String role, Map<String, String> roleInterpolationMap) {
protected String interpolateRole(String role, Map<String, String> roleInterpolationMap) {
Matcher matcher = ROLE_PARAM_INTERPOLATOR_REGEX.matcher(role);
StringBuffer interpolatedRole = new StringBuffer();
while(matcher.find()){
Expand All @@ -99,7 +108,7 @@ protected static String interpolateRole(String role, Map<String, String> roleInt
/**
* A compound permission which is true if any of the underlying permissions is true
*/
public static Permission anyOf(final Permission... permissions) {
public Permission anyOf(final Permission... permissions) {
return new Permission() {
@Override
public Optional<? extends Permission> has(RestxPrincipal principal, Map<String, String> roleInterpolationMap) {
Expand All @@ -123,7 +132,7 @@ public String toString() {
/**
* A compound permission which is true if all underlying permissions are true
*/
public static Permission allOf(final Permission... permissions) {
public Permission allOf(final Permission... permissions) {
return new Permission() {
@Override
public Optional<? extends Permission> has(RestxPrincipal principal, Map<String, String> roleInterpolationMap) {
Expand Down
Expand Up @@ -51,19 +51,22 @@ public class RestxSessionCookieFilter implements RestxRouteFilter, RestxHandler
private final RestxSession.Definition sessionDefinition;
private final ObjectMapper mapper;
private final Signer signer;
private final RestxSessionCookieDescriptor restxSessionCookieDescriptor;
private final PermissionFactory permissionFactory;
private final RestxSessionCookieDescriptor restxSessionCookieDescriptor;
private final RestxSession emptySession;

public RestxSessionCookieFilter(
RestxSession.Definition sessionDefinition,
@Named(FrontObjectMapperFactory.MAPPER_NAME) ObjectMapper mapper,
@Named(COOKIE_SIGNER_NAME) Signer signer,
PermissionFactory permissionFactory,
RestxSessionCookieDescriptor restxSessionCookieDescriptor) {

this.sessionDefinition = sessionDefinition;
this.mapper = mapper;
this.signer = signer;
this.restxSessionCookieDescriptor = restxSessionCookieDescriptor;
this.permissionFactory = permissionFactory;
this.restxSessionCookieDescriptor = restxSessionCookieDescriptor;
this.emptySession = new RestxSession(sessionDefinition, ImmutableMap.<String, String>of(),
Optional.<RestxPrincipal>absent(), Duration.ZERO);
}
Expand Down Expand Up @@ -124,7 +127,7 @@ public RestxSession buildContextFromRequest(RestxRequest req) throws IOException
Optional<RestxPrincipal> principalOptional = RestxSession.getValue(
sessionDefinition, RestxPrincipal.class, RestxPrincipal.SESSION_DEF_KEY, principalName);
if (principalOptional.isPresent()
&& Permissions.hasRole("restx-admin").has(principalOptional.get(), Collections.<String,String>emptyMap()).isPresent()) {
&& permissionFactory.hasRole("restx-admin").has(principalOptional.get(), Collections.<String,String>emptyMap()).isPresent()) {
Optional<String> su = req.getHeader("RestxSu");
if (su.isPresent() && !Strings.isNullOrEmpty(su.get())) {
try {
Expand Down
Expand Up @@ -24,9 +24,15 @@
public class StdRestxSecurityManager implements RestxSecurityManager {
private static final Logger logger = LoggerFactory.getLogger(StdRestxSecurityManager.class);

protected final PermissionFactory permissionFactory;

public StdRestxSecurityManager(PermissionFactory permissionFactory) {
this.permissionFactory = permissionFactory;
}

@Override
public void check(RestxRequest request, RestxRequestMatch requestMatch, Permission permission) {
if (permission == Permissions.open()) {
if (permissionFactory.isOpen(permission)) {
return;
}

Expand Down
@@ -1,10 +1,8 @@
package restx.factory;

import static restx.security.Permissions.hasRole;


import restx.*;
import restx.admin.AdminModule;
import restx.security.PermissionFactory;
import restx.security.RestxSecurityManager;

import javax.inject.Inject;
Expand All @@ -14,17 +12,19 @@
public class FactoryDumpRoute extends StdRoute {
private final Factory factory;
private final RestxSecurityManager securityManager;
private PermissionFactory permissionFactory;

@Inject
public FactoryDumpRoute(Factory factory, RestxSecurityManager securityManager) {
public FactoryDumpRoute(Factory factory, RestxSecurityManager securityManager, PermissionFactory permissionFactory) {
super("FactoryRoute", new StdRestxRequestMatcher("GET", "/@/factory"));
this.factory = factory;
this.securityManager = securityManager;
this.permissionFactory = permissionFactory;
}

@Override
public void handle(RestxRequestMatch match, RestxRequest req, RestxResponse resp, RestxContext ctx) throws IOException {
securityManager.check(req, match, hasRole(AdminModule.RESTX_ADMIN_ROLE));
securityManager.check(req, match, permissionFactory.hasRole(AdminModule.RESTX_ADMIN_ROLE));
resp.setContentType("text/plain");
resp.getWriter().println(factory.dump());
}
Expand Down

0 comments on commit ba28876

Please sign in to comment.