Skip to content

Commit

Permalink
Reworks role based resource validation. (#1288)
Browse files Browse the repository at this point in the history
-- We fully generalize RoleBasedTableValidatorRequestMapper to apply to
any ApiRequest, not just DataApiRequest, and in the process, rename it
to `RoleBasedValidatorRequestMapper` since it no longer
necessarily involves tables.

-- RoleBasedTableValidatorRequestMapper becomes a subclass of
`RoleBasedValidatorRequestMapper` specifically for TableApiRequests.
The subclass delegates to its parent if the request contains a table
(i.e. the user requests information about a specific table). Otherwise,
if there is no table on the request (i.e. the user asks for
information about all tables), the
`RoleBasedTableValidatorRequestMapper` instead modifies the
TableApiRequest to only expose those tables that pass validation.

Co-authored-by: Andrew Cholewa <acholewa@verizonmedia.com>
  • Loading branch information
Andrew Cholewa and Andrew Cholewa authored Dec 19, 2022
1 parent 7402642 commit b0c18d2
Show file tree
Hide file tree
Showing 5 changed files with 374 additions and 103 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ Current
### Added:

### Changed:
- [Reworked RoleBased Request Mapping for greater generality and yet more flexibility](https://github.com/yahoo/fili/pull/1288)
- `RoleBasedValidatorRequestMapper` has been added that applies to any ApiRequest, but otherwise behaves like the original
`RoleBasedTableValidatorRequestMapper`.
- The `RoleBasedTableValidatorRequestMapper` is now specific to the TablesApiRequest and handles requests for specific tables and
fullview requests.


- [Updated Groovy to 3.0](https://github.com/yahoo/fili/pull/1171)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,29 @@
package com.yahoo.bard.webservice.web.security;

import com.yahoo.bard.webservice.data.config.ResourceDictionaries;
import com.yahoo.bard.webservice.web.ChainingRequestMapper;
import com.yahoo.bard.webservice.table.LogicalTable;
import com.yahoo.bard.webservice.web.RequestMapper;
import com.yahoo.bard.webservice.web.RequestValidationException;
import com.yahoo.bard.webservice.web.apirequest.DataApiRequest;
import com.yahoo.bard.webservice.web.apirequest.TablesApiRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.LinkedHashSet;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.validation.constraints.NotNull;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;

/**
* A RequestMapper that validates table access for user based on roles that a user is associated with.
*
* @param <T> Type of API Request this RequestMapper will work on
*/
public class RoleBasedTableValidatorRequestMapper<T extends DataApiRequest> extends ChainingRequestMapper<T> {

public static String DEFAULT_SECURITY_MAPPER_NAME = "__default";

public static Predicate<SecurityContext> NO_OP_PREDICATE = (ignored -> true);
public class RoleBasedTableValidatorRequestMapper extends RoleBasedValidatorRequestMapper<TablesApiRequest> {

private final Map<String, Predicate<SecurityContext>> securityRules;

private final Function<T, String> securityContextSelector;
private static final Logger LOG = LoggerFactory.getLogger(RoleBasedTableValidatorRequestMapper.class);

/**
* Constructor.
Expand All @@ -41,42 +36,21 @@ public class RoleBasedTableValidatorRequestMapper<T extends DataApiRequest> exte
public RoleBasedTableValidatorRequestMapper(
Map<String, Predicate<SecurityContext>> securityRules,
ResourceDictionaries resourceDictionaries,
@NotNull RequestMapper<T> next
@NotNull RequestMapper<TablesApiRequest> next
) {
this(securityRules, resourceDictionaries, next, r -> r.getTable().getName());
}

/**
* Constructor.
* @param securityRules A map of predicates for validating table access.
* @param resourceDictionaries The dictionaries to use for request mapping.
* @param securityContextSelector A function for selecting a security group name from the context.
* @param next The next request mapper to process this ApiRequest
*/
public RoleBasedTableValidatorRequestMapper(
Map<String, Predicate<SecurityContext>> securityRules,
ResourceDictionaries resourceDictionaries,
@NotNull RequestMapper<T> next,
Function<T, String> securityContextSelector
) {
super(resourceDictionaries, next);
this.securityRules = securityRules;
this.securityContextSelector = securityContextSelector;
super(securityRules, resourceDictionaries, next, r -> r.getTable().getName());
}

@Override
public T internalApply(T request, ContainerRequestContext context)
public TablesApiRequest internalApply(TablesApiRequest request, ContainerRequestContext context)
throws RequestValidationException {
SecurityContext securityContext = context.getSecurityContext();
String securityTag = securityContextSelector.apply(request);
Predicate<SecurityContext> isAllowed = securityRules.containsKey(securityTag) ?
securityRules.get(securityTag) :
securityRules.getOrDefault(DEFAULT_SECURITY_MAPPER_NAME, NO_OP_PREDICATE);

if (!isAllowed.test(securityContext)) {
throw new RequestValidationException(Response.Status.FORBIDDEN, "Permission Denied",
"Request cannot be completed as you do not have enough permission");
if (request.getTable() == null) {
SecurityContext securityContext = context.getSecurityContext();
LinkedHashSet<LogicalTable> exposedTables = request.getTables().stream()
.filter(table -> validate(table.getName(), securityContext))
.collect(Collectors.toCollection(LinkedHashSet::new));
return request.withTables(exposedTables);
}
return request;
return super.internalApply(request, context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2018 Yahoo Inc.
// Licensed under the terms of the Apache license. Please see LICENSE.md file distributed with this work for terms.
package com.yahoo.bard.webservice.web.security;

import com.yahoo.bard.webservice.data.config.ResourceDictionaries;
import com.yahoo.bard.webservice.web.ChainingRequestMapper;
import com.yahoo.bard.webservice.web.RequestMapper;
import com.yahoo.bard.webservice.web.RequestValidationException;
import com.yahoo.bard.webservice.web.apirequest.ApiRequest;

import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;

import javax.validation.constraints.NotNull;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;

/**
* A RequestMapper that validates resource access for user based on roles that a user is associated with.
*
* @param <T> Type of API Request this RequestMapper will work on
*/
public class RoleBasedValidatorRequestMapper<T extends ApiRequest> extends ChainingRequestMapper<T> {

public static String DEFAULT_SECURITY_MAPPER_NAME = "__default";

public static Predicate<SecurityContext> NO_OP_PREDICATE = (ignored -> true);

private final Map<String, Predicate<SecurityContext>> securityRules;

private final Function<T, String> securityIdSelector;

/**
* Constructor.
* @param securityRules A map of predicates for validating table access.
* @param resourceDictionaries The dictionaries to use for request mapping.
* @param securityIdSelector A function for selecting a security group name from the context.
* @param next The next request mapper to process this ApiRequest
*/
public RoleBasedValidatorRequestMapper(
Map<String, Predicate<SecurityContext>> securityRules,
ResourceDictionaries resourceDictionaries,
@NotNull RequestMapper<T> next,
Function<T, String> securityIdSelector
) {
super(resourceDictionaries, next);
this.securityRules = securityRules;
this.securityIdSelector = securityIdSelector;
}

@Override
public T internalApply(T request, ContainerRequestContext context)
throws RequestValidationException {
if (!validate(securityIdSelector.apply(request), context.getSecurityContext())) {
throw new RequestValidationException(Response.Status.FORBIDDEN, "Permission Denied",
"Request cannot be completed as you do not have enough permission");
}
return request;
}

protected boolean validate(String securityTag, SecurityContext context) {
Predicate<SecurityContext> isAllowed = securityRules.containsKey(securityTag) ?
securityRules.get(securityTag) :
securityRules.getOrDefault(DEFAULT_SECURITY_MAPPER_NAME, NO_OP_PREDICATE);
return isAllowed.test(context);

}
}
Loading

0 comments on commit b0c18d2

Please sign in to comment.