GUACAMOLE-641: Provide strict filtering mode for TokenFilter which disallows undefined tokens.

This commit is contained in:
Michael Jumper
2022-01-21 15:23:40 -08:00
parent 19920eeed1
commit 0ac67b8cf8
2 changed files with 206 additions and 8 deletions

View File

@@ -0,0 +1,96 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.guacamole.token;
import org.apache.guacamole.GuacamoleServerException;
/**
* An exception thrown when a token cannot be substituted because it has no
* corresponding value. Additional information describing the undefined token
* is provided.
*/
public class GuacamoleTokenUndefinedException extends GuacamoleServerException {
/**
* The name of the token that is undefined.
*/
private final String tokenName;
/**
* Creates a new GuacamoleTokenUndefinedException with the given message,
* cause, and associated undefined token name.
*
* @param message
* A human readable description of the exception that occurred.
*
* @param cause
* The cause of this exception.
*
* @param tokenName
* The name of the token which has no defined value.
*/
public GuacamoleTokenUndefinedException(String message, Throwable cause,
String tokenName) {
super(message, cause);
this.tokenName = tokenName;
}
/**
* Creates a new GuacamoleTokenUndefinedException with the given
* message and associated undefined token name.
*
* @param message
* A human readable description of the exception that occurred.
*
* @param tokenName
* The name of the token which has no defined value.
*/
public GuacamoleTokenUndefinedException(String message, String tokenName) {
super(message);
this.tokenName = tokenName;
}
/**
* Creates a new GuacamoleTokenUndefinedException with the given cause
* and associated undefined token name.
*
* @param cause
* The cause of this exception.
*
* @param tokenName
* The name of the token which has no defined value.
*/
public GuacamoleTokenUndefinedException(Throwable cause, String tokenName) {
super(cause);
this.tokenName = tokenName;
}
/**
* Returns the name of the token which has no defined value, causing this
* exception to be thrown.
*
* @return
* The name of the token which has no defined value.
*/
public String getTokenName() {
return tokenName;
}
}

View File

@@ -162,19 +162,31 @@ public class TokenFilter {
tokenValues.clear();
tokenValues.putAll(tokens);
}
/**
* Filters the given string, replacing any tokens with their corresponding
* values.
* values. Handling of undefined tokens depends on the value given for the
* strict flag.
*
* @param input
* The string to filter.
*
* @param strict
* Whether to disallow tokens which lack values from existing in the
* string. If true, an exception will be thrown if any tokens in the
* string lack corresponding values. If false, tokens which lack values
* will be interpreted as literals.
*
* @return
* A copy of the input string, with any tokens replaced with their
* corresponding values.
*
* @throws GuacamoleTokenUndefinedException
* If the strict flag is set to true and at least one token in the
* given string has no corresponding value.
*/
public String filter(String input) {
private String filter(String input, boolean strict)
throws GuacamoleTokenUndefinedException {
StringBuilder output = new StringBuilder();
Matcher tokenMatcher = tokenPattern.matcher(input);
@@ -209,10 +221,20 @@ public class TokenFilter {
String tokenName = tokenMatcher.group(TOKEN_NAME_GROUP);
String tokenValue = getToken(tokenName);
// If token is unknown, interpret as literal
// If token is unknown, interpretation depends on whether
// strict mode is enabled
if (tokenValue == null) {
// Fail outright if strict mode is enabled
if (strict)
throw new GuacamoleTokenUndefinedException("Token "
+ "has no defined value.", tokenName);
// If strict mode is NOT enabled, simply interpret as
// a literal
String notToken = tokenMatcher.group(TOKEN_GROUP);
output.append(notToken);
}
// Otherwise, check for modifiers and substitute value appropriately
@@ -247,19 +269,71 @@ public class TokenFilter {
// Update last regex match
endOfLastMatch = tokenMatcher.end();
}
// Append any remaining non-token text
output.append(input.substring(endOfLastMatch));
return output.toString();
}
/**
* Filters the given string, replacing any tokens with their corresponding
* values. Any tokens present in the given string which lack values will
* be interpreted as literals.
*
* @param input
* The string to filter.
*
* @return
* A copy of the input string, with any tokens replaced with their
* corresponding values.
*/
public String filter(String input) {
// Filter with strict mode disabled (should always succeed)
try {
return filter(input, false);
}
// GuacamoleTokenUndefinedException cannot be thrown when strict mode
// is disabled
catch (GuacamoleTokenUndefinedException e) {
throw new IllegalStateException("filter() threw "
+ "GuacamoleTokenUndefinedException despite strict mode "
+ "being disabled.");
}
}
/**
* Filters the given string, replacing any tokens with their corresponding
* values. If any token in the given string has no defined value within
* this TokenFilter, a GuacamoleTokenUndefinedException will be thrown.
*
* @param input
* The string to filter.
*
* @return
* A copy of the input string, with any tokens replaced with their
* corresponding values.
*
* @throws GuacamoleTokenUndefinedException
* If at least one token in the given string has no corresponding
* value.
*/
public String filterStrict(String input)
throws GuacamoleTokenUndefinedException {
return filter(input, true);
}
/**
* Given an arbitrary map containing String values, replace each non-null
* value with the corresponding filtered value.
* value with the corresponding filtered value. Any tokens present in the
* values of the given map which lack defined values within this
* TokenFilter will be interpreted as literals.
*
* @param map
* The map whose values should be filtered.
@@ -273,6 +347,34 @@ public class TokenFilter {
String value = entry.getValue();
if (value != null)
entry.setValue(filter(value));
}
}
/**
* Given an arbitrary map containing String values, replace each non-null
* value with the corresponding filtered value. If any token in any string
* has no defined value within this TokenFilter, a
* GuacamoleTokenUndefinedException will be thrown.
*
* @param map
* The map whose values should be filtered.
*
* @throws GuacamoleTokenUndefinedException
* If at least one token in at least one string has no corresponding
* value.
*/
public void filterValuesStrict(Map<?, String> map)
throws GuacamoleTokenUndefinedException {
// For each map entry
for (Map.Entry<?, String> entry : map.entrySet()) {
// If value is non-null, filter value through this TokenFilter
String value = entry.getValue();
if (value != null)
entry.setValue(filterStrict(value));
}