Skip to content

Commit 2067976

Browse files
committed
Ref #25533: allow using names for parameter values
1 parent 9443bd1 commit 2067976

7 files changed

Lines changed: 309 additions & 179 deletions

File tree

src/main/java/eu/openanalytics/containerproxy/model/runtime/AllowedParametersForUser.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@
2929
public class AllowedParametersForUser {
3030

3131
/**
32-
* Mapping of ParameterName to a list of allowed values.
33-
* The integer represents the index of the parameter.
34-
* The list is sorted by the index.
32+
* Mapping of the id of the parameter to the (human friendly) names.
3533
*/
3634
private final Map<String, List<String>> values;
3735

src/main/java/eu/openanalytics/containerproxy/model/spec/ParameterDefinition.java

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,43 +20,93 @@
2020
*/
2121
package eu.openanalytics.containerproxy.model.spec;
2222

23-
import org.apache.commons.lang3.StringUtils;
24-
import org.apache.logging.log4j.util.Strings;
23+
import org.apache.commons.collections4.BidiMap;
24+
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
25+
import org.springframework.boot.context.properties.ConstructorBinding;
2526

27+
import java.util.List;
28+
29+
@ConstructorBinding
2630
public class ParameterDefinition {
2731

28-
private String id;
29-
private String displayName;
30-
private String description;
32+
private final String id;
33+
private final String displayName;
34+
private final String description;
35+
36+
// a mapping of the raw value (used in the backend) to a human friendly name used in the front-end
37+
private final BidiMap<String, String> valueNames;
38+
39+
public ParameterDefinition(String id, String displayName, String description, List<ValueName> valueNames) {
40+
this.id = id;
41+
this.displayName = displayName;
42+
this.description = description;
43+
this.valueNames = new DualHashBidiMap<>();
44+
if (valueNames != null) {
45+
for (ValueName valueName : valueNames) {
46+
if (this.valueNames.containsKey(valueName.value)) {
47+
throw new IllegalStateException(String.format("A ValueName mapping already contains a mapping for value \"%s\"", valueName.value));
48+
}
49+
if (this.valueNames.containsValue(valueName.name)) {
50+
throw new IllegalStateException(String.format("A ValueName mapping already contains a mapping with the name \"%s\"", valueName.name));
51+
}
52+
this.valueNames.put(valueName.value, valueName.name);
53+
}
54+
}
55+
}
3156

3257
public String getDisplayName() {
3358
return displayName;
3459
}
3560

61+
// used in Thymeleaf template!
62+
@SuppressWarnings("unused")
3663
public String getDisplayNameOrId() {
3764
if (displayName != null) {
3865
return displayName;
3966
}
4067
return id;
4168
}
4269

43-
public void setDisplayName(String displayName) {
44-
this.displayName = displayName;
45-
}
46-
4770
public String getDescription() {
4871
return description;
4972
}
5073

51-
public void setDescription(String description) {
52-
this.description = description;
53-
}
54-
5574
public String getId() {
5675
return id;
5776
}
5877

59-
public void setId(String id) {
60-
this.id = id;
78+
/**
79+
* Given the (backend) value, return the human friendly name for the value.
80+
* @param value the backend-value
81+
* @return the human friendly name of the value
82+
*/
83+
public String getNameOfValue(String value) {
84+
return valueNames.getOrDefault(value, value);
85+
}
86+
87+
public boolean hasNameForValue(String value) {
88+
return valueNames.containsKey(value);
89+
}
90+
91+
/**
92+
* Given the (human friendly name), return the backend value
93+
* @param name the human-friendly name
94+
* @return the backend value
95+
*/
96+
public String getValueForName(String name) {
97+
return valueNames.getKey(name);
6198
}
99+
100+
@ConstructorBinding
101+
public static class ValueName {
102+
103+
private final String name;
104+
private final String value;
105+
106+
public ValueName(String name, String value) {
107+
this.name = name;
108+
this.value = value;
109+
}
110+
}
111+
62112
}

src/main/java/eu/openanalytics/containerproxy/service/ParametersService.java

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,16 @@
2727
import eu.openanalytics.containerproxy.model.spec.ProxySpec;
2828
import eu.openanalytics.containerproxy.spec.IProxySpecProvider;
2929
import org.apache.commons.lang3.StringUtils;
30-
import org.apache.commons.lang3.tuple.Pair;
3130
import org.springframework.security.core.Authentication;
3231
import org.springframework.stereotype.Service;
3332

3433
import javax.annotation.PostConstruct;
3534
import java.util.ArrayList;
36-
import java.util.Comparator;
3735
import java.util.HashMap;
3836
import java.util.HashSet;
3937
import java.util.List;
4038
import java.util.Map;
39+
import java.util.Optional;
4140
import java.util.Set;
4241
import java.util.regex.Pattern;
4342
import java.util.stream.Collectors;
@@ -118,10 +117,21 @@ private void validateSpec(ProxySpec spec) {
118117

119118
}
120119

121-
public boolean validateRequest(Authentication auth, ProxySpec resolvedSpec, ProvidedParameters providedParameters) throws InvalidParametersException {
122-
Parameters parameters = resolvedSpec.getParameters();
120+
/**
121+
* Parses and validates the parameters provided by a user.
122+
* - checks that a value is included for every parameter
123+
* - checks that the user is allowed to use these values
124+
* - converts the (human friendly) name to the backend value
125+
* @param auth the user
126+
* @param spec the Proxy spec to which this requests belong
127+
* @param providedParameters the parameters as provided by the user (using human friendly names)
128+
* @return the parsed parameters (using backend values)
129+
* @throws InvalidParametersException
130+
*/
131+
public Optional<ProvidedParameters> parseAndValidateRequest(Authentication auth, ProxySpec spec, ProvidedParameters providedParameters) throws InvalidParametersException {
132+
Parameters parameters = spec.getParameters();
123133
if (parameters == null) {
124-
return false;
134+
return Optional.empty();
125135
}
126136

127137
if (providedParameters == null) {
@@ -142,29 +152,54 @@ public boolean validateRequest(Authentication auth, ProxySpec resolvedSpec, Prov
142152

143153
// check if the combination of values is allowed
144154
for (Parameters.ValueSet valueSet : parameters.getValueSets()) {
145-
if (!accessControlEvaluationService.checkAccess(auth, resolvedSpec, valueSet.getAccessControl())) {
155+
if (!accessControlEvaluationService.checkAccess(auth, spec, valueSet.getAccessControl())) {
146156
continue;
147157
}
148-
if (areParametersAllowedByValueSet(parameters.getIds(), valueSet, providedParameters)) {
149-
return true; // parameters are allowed
158+
Optional<ProvidedParameters> res = convertParametersIfAllowed(parameters.getDefinitions(), valueSet, providedParameters);
159+
if (res.isPresent()) {
160+
return res; // parameters are allowed, return the converted values
150161
}
151162
}
152163

153164
throw new InvalidParametersException("Provided parameter values are not allowed");
154165
}
155166

156-
private boolean areParametersAllowedByValueSet(List<String> parameterIds, Parameters.ValueSet valueSet, ProvidedParameters providedParameters) {
157-
for (String parameterId : parameterIds) {
158-
if (!providedParameters.containsParameter(parameterId)) {
159-
throw new IllegalStateException("Could not find value for parameter with id" + parameterId);
167+
/**
168+
* Checks whether the provided parameters are allowed by the given valueSet.
169+
* Returns the converted backend values if (and only if) the provided human-friendly values are allowed by this
170+
* valueSet.
171+
* @param parameters the parameter defintiions
172+
* @param valueSet the valueSet to check
173+
* @param providedParameters the parameters as provided by the user (using human friendly names)
174+
* @return the converted values (i.e. using backend values) if allowed otherwise nothing
175+
*/
176+
private Optional<ProvidedParameters> convertParametersIfAllowed(List<ParameterDefinition> parameters, Parameters.ValueSet valueSet, ProvidedParameters providedParameters) {
177+
Map<String, String> res = new HashMap<>();
178+
for (ParameterDefinition parameter: parameters) {
179+
if (!providedParameters.containsParameter(parameter.getId())) {
180+
throw new IllegalStateException("Could not find value for parameter with id" + parameter.getId());
181+
}
182+
String providedValue = providedParameters.getValue(parameter.getId());
183+
String backendValue = parameter.getValueForName(providedValue);
184+
if (backendValue == null) {
185+
// if we did not find a backend value for the provided value (i.e. the user already provided a backend value),
186+
// check that no mapping exists for this backend value.
187+
// The backend value can only be used if a mapping does not exist.
188+
if (parameter.hasNameForValue(providedValue)) {
189+
return Optional.empty();
190+
} else {
191+
backendValue = providedValue;
192+
}
160193
}
161-
String providedValue = providedParameters.getValue(parameterId);
162-
if (!valueSet.getParameterValues(parameterId).contains(providedValue)) {
163-
return false;
194+
// check whether the backendValue is in the list of allowed values of this valueSet
195+
if (!valueSet.getParameterValues(parameter.getId()).contains(backendValue)) {
196+
return Optional.empty();
164197
}
198+
res.put(parameter.getId(), backendValue);
165199
}
166200
// providedParameters contains an allowed value for every parameter
167-
return true;
201+
// return the backend values (instead of the names provided by the user)
202+
return Optional.of(new ProvidedParameters(res));
168203
}
169204

170205
public AllowedParametersForUser calculateAllowedParametersForUser(Authentication auth, ProxySpec proxySpec) {
@@ -174,7 +209,7 @@ public AllowedParametersForUser calculateAllowedParametersForUser(Authentication
174209
}
175210
List<String> parameterIds = parameters.getIds();
176211

177-
// 1. check which ValueSets are allowed for this
212+
// 1. check which ValueSets are allowed for this user
178213
List<Parameters.ValueSet> allowedValueSets = parameters.getValueSets().stream()
179214
.filter(v -> accessControlEvaluationService.checkAccess(auth, proxySpec, v.getAccessControl()))
180215
.collect(Collectors.toList());
@@ -186,25 +221,24 @@ public AllowedParametersForUser calculateAllowedParametersForUser(Authentication
186221
// for every set of allowed values
187222
for (Parameters.ValueSet valueSet : allowedValueSets) {
188223
// for every parameter in this set
189-
for (String parameterId : parameterIds) {
190-
valuesToIndex.computeIfAbsent(parameterId, (k) -> new HashMap<>());
191-
values.computeIfAbsent(parameterId, (k) -> new ArrayList<>());
224+
for (ParameterDefinition parameter : parameters.getDefinitions()) {
225+
valuesToIndex.computeIfAbsent(parameter.getId(), (k) -> new HashMap<>());
226+
values.computeIfAbsent(parameter.getId(), (k) -> new ArrayList<>());
192227
// for every value of this parameter
193-
for (String value : valueSet.getParameterValues(parameterId)) {
194-
if (!valuesToIndex.get(parameterId).containsKey(value)) {
228+
for (String value : valueSet.getParameterValues(parameter.getId())) {
229+
if (!valuesToIndex.get(parameter.getId()).containsKey(value)) {
195230
// add it to allValues if it does not yet exist
196-
Integer newIndex = values.get(parameterId).size() + 1;
197-
valuesToIndex.get(parameterId).put(value, newIndex);
198-
values.get(parameterId).add(value);
231+
Integer newIndex = values.get(parameter.getId()).size() + 1;
232+
valuesToIndex.get(parameter.getId()).put(value, newIndex);
233+
values.get(parameter.getId()).add(parameter.getNameOfValue(value));
199234
}
200235
}
201236
}
202237
}
203238

204-
// 3. compute the set of allowed values
239+
// 3. compute the set of allowed values for every value-set
205240
HashSet<List<Integer>> allowedCombinations = new HashSet<>();
206241

207-
// for every value-set
208242
for (Parameters.ValueSet valueSet : allowedValueSets) {
209243
allowedCombinations.addAll(getAllowedCombinationsForSingleValueSet(parameterIds, valueSet, valuesToIndex));
210244
}

src/main/java/eu/openanalytics/containerproxy/service/RuntimeValueService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.stereotype.Service;
3434

3535
import javax.inject.Inject;
36+
import java.util.Optional;
3637

3738
/**
3839
* Determines the RuntimeValue when a proxy gets started.
@@ -68,8 +69,9 @@ public void init() {
6869
}
6970

7071
public void createRuntimeValues(ProxySpec spec, ProvidedParameters parameters, Proxy proxy) throws InvalidParametersException {
71-
if (parametersService.validateRequest(userService.getCurrentAuth(), spec, parameters)) {
72-
proxy.addRuntimeValue(new RuntimeValue(ParametersKey.inst, parameters));
72+
Optional<ProvidedParameters> providedParametersOptional = parametersService.parseAndValidateRequest(userService.getCurrentAuth(), spec, parameters);
73+
if (providedParametersOptional.isPresent()) {
74+
proxy.addRuntimeValue(new RuntimeValue(ParametersKey.inst, providedParametersOptional.get()));
7375
}
7476
SpecExpressionContext context = SpecExpressionContext.create(
7577
proxy,

0 commit comments

Comments
 (0)