mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUACAMOLE-641: Merge support for reading secrets from key vaults.
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
|
||||
package org.apache.guacamole.net.auth;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
@@ -28,7 +29,9 @@ import org.apache.guacamole.protocol.GuacamoleClientInformation;
|
||||
/**
|
||||
* Connection implementation which overrides the connect() function of an
|
||||
* underlying Connection, adding a given set of parameter tokens to the tokens
|
||||
* already supplied.
|
||||
* already supplied. If not supplying a static set of tokens at construction
|
||||
* time, implementations should override either {@link #addTokens(java.util.Map)}
|
||||
* or {@link #getTokens()} to provide tokens dynamically.
|
||||
*/
|
||||
public class TokenInjectingConnection extends DelegatingConnection {
|
||||
|
||||
@@ -37,6 +40,39 @@ public class TokenInjectingConnection extends DelegatingConnection {
|
||||
*/
|
||||
private final Map<String, String> tokens;
|
||||
|
||||
/**
|
||||
* Returns the tokens which should be added to an in-progress call to
|
||||
* connect(). If not overridden, this function will return the tokens
|
||||
* provided when this instance of TokenInjectingConnection was created. If
|
||||
* the values of existing tokens need to be considered, implementations
|
||||
* should override {@link #addTokens(java.util.Map)} instead.
|
||||
*
|
||||
* @return
|
||||
* The tokens which should be added to the in-progress call to
|
||||
* connect().
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the applicable tokens cannot be generated.
|
||||
*/
|
||||
protected Map<String, String> getTokens() throws GuacamoleException {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tokens to an in-progress call to connect(). If not overridden, this
|
||||
* function will add the tokens returned by {@link #getTokens()}.
|
||||
*
|
||||
* @param tokens
|
||||
* A modifiable Map containing the tokens already supplied to
|
||||
* connect().
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the applicable tokens cannot be generated.
|
||||
*/
|
||||
protected void addTokens(Map<String, String> tokens) throws GuacamoleException {
|
||||
tokens.putAll(getTokens());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the given Connection, automatically adding the given tokens to
|
||||
* each invocation of connect(). Any additional tokens which have the same
|
||||
@@ -54,13 +90,27 @@ public class TokenInjectingConnection extends DelegatingConnection {
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the given Connection such that the additional parameter tokens
|
||||
* added by {@link #addTokens(java.util.Map)} or returned by
|
||||
* {@link #getTokens()} are included with each invocation of connect().
|
||||
* Any additional tokens which have the same name as existing tokens will
|
||||
* override the existing values.
|
||||
*
|
||||
* @param connection
|
||||
* The Connection to wrap.
|
||||
*/
|
||||
public TokenInjectingConnection(Connection connection) {
|
||||
this(connection, Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuacamoleTunnel connect(GuacamoleClientInformation info,
|
||||
Map<String, String> tokens) throws GuacamoleException {
|
||||
|
||||
// Apply provided tokens over those given to connect()
|
||||
tokens = new HashMap<>(tokens);
|
||||
tokens.putAll(this.tokens);
|
||||
addTokens(tokens);
|
||||
|
||||
return super.connect(info, tokens);
|
||||
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
package org.apache.guacamole.net.auth;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
@@ -28,7 +29,10 @@ import org.apache.guacamole.protocol.GuacamoleClientInformation;
|
||||
/**
|
||||
* ConnectionGroup implementation which overrides the connect() function of an
|
||||
* underlying ConnectionGroup, adding a given set of parameter tokens to the
|
||||
* tokens already supplied.
|
||||
* tokens already supplied. If not supplying a static set of tokens at
|
||||
* construction time, implementations should override either
|
||||
* {@link #addTokens(java.util.Map)} or {@link #getTokens()} to provide tokens
|
||||
* dynamically.
|
||||
*/
|
||||
public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup {
|
||||
|
||||
@@ -37,6 +41,39 @@ public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup {
|
||||
*/
|
||||
private final Map<String, String> tokens;
|
||||
|
||||
/**
|
||||
* Returns the tokens which should be added to an in-progress call to
|
||||
* connect(). If not overridden, this function will return the tokens
|
||||
* provided when this instance of TokenInjectingConnection was created. If
|
||||
* the values of existing tokens need to be considered, implementations
|
||||
* should override {@link #addTokens(java.util.Map)} instead.
|
||||
*
|
||||
* @return
|
||||
* The tokens which should be added to the in-progress call to
|
||||
* connect().
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the applicable tokens cannot be generated.
|
||||
*/
|
||||
protected Map<String, String> getTokens() throws GuacamoleException {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tokens to an in-progress call to connect(). If not overridden, this
|
||||
* function will add the tokens returned by {@link #getTokens()}.
|
||||
*
|
||||
* @param tokens
|
||||
* A modifiable Map containing the tokens already supplied to
|
||||
* connect().
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the applicable tokens cannot be generated.
|
||||
*/
|
||||
protected void addTokens(Map<String, String> tokens) throws GuacamoleException {
|
||||
tokens.putAll(getTokens());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the given ConnectionGroup, automatically adding the given tokens
|
||||
* to each invocation of connect(). Any additional tokens which have the
|
||||
@@ -54,13 +91,27 @@ public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup {
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the given ConnectionGroup such that the additional parameter
|
||||
* tokens added by {@link #addTokens(java.util.Map)} or returned by
|
||||
* {@link #getTokens()} are included with each invocation of connect(). Any
|
||||
* additional tokens which have the same name as existing tokens will
|
||||
* override the existing values.
|
||||
*
|
||||
* @param connectionGroup
|
||||
* The ConnectionGroup to wrap.
|
||||
*/
|
||||
public TokenInjectingConnectionGroup(ConnectionGroup connectionGroup) {
|
||||
this(connectionGroup, Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuacamoleTunnel connect(GuacamoleClientInformation info,
|
||||
Map<String, String> tokens) throws GuacamoleException {
|
||||
|
||||
// Apply provided tokens over those given to connect()
|
||||
tokens = new HashMap<>(tokens);
|
||||
tokens.putAll(this.tokens);
|
||||
addTokens(tokens);
|
||||
|
||||
return super.connect(info, tokens);
|
||||
|
||||
|
@@ -32,7 +32,7 @@ public class TokenInjectingUserContext extends DelegatingUserContext {
|
||||
|
||||
/**
|
||||
* The additional tokens to include with each call to connect() if
|
||||
* getTokens() is not overridden.
|
||||
* getTokens() or addTokens() are not overridden.
|
||||
*/
|
||||
private final Map<String, String> tokens;
|
||||
|
||||
@@ -42,8 +42,8 @@ public class TokenInjectingUserContext extends DelegatingUserContext {
|
||||
* parameter tokens are included. Any additional tokens which have the same
|
||||
* name as existing tokens will override the existing values. If tokens
|
||||
* specific to a particular connection or connection group need to be
|
||||
* included, getTokens() may be overridden to provide a different set of
|
||||
* tokens.
|
||||
* included, getTokens() or addTokens() may be overridden to provide a
|
||||
* different set of tokens.
|
||||
*
|
||||
* @param userContext
|
||||
* The UserContext to wrap.
|
||||
@@ -60,9 +60,9 @@ public class TokenInjectingUserContext extends DelegatingUserContext {
|
||||
/**
|
||||
* Wraps the given UserContext, overriding the connect() function of each
|
||||
* retrieved Connection and ConnectionGroup such that the additional
|
||||
* parameter tokens returned by getTokens() are included. Any additional
|
||||
* tokens which have the same name as existing tokens will override the
|
||||
* existing values.
|
||||
* parameter tokens added by addTokens() or returned by getTokens() are
|
||||
* included. Any additional tokens which have the same name as existing
|
||||
* tokens will override the existing values.
|
||||
*
|
||||
* @param userContext
|
||||
* The UserContext to wrap.
|
||||
@@ -75,7 +75,10 @@ public class TokenInjectingUserContext extends DelegatingUserContext {
|
||||
* Returns the tokens which should be added to an in-progress call to
|
||||
* connect() for the given Connection. If not overridden, this function
|
||||
* will return the tokens provided when this instance of
|
||||
* TokenInjectingUserContext was created.
|
||||
* TokenInjectingUserContext was created. If the values of existing tokens
|
||||
* need to be considered, implementations should override
|
||||
* {@link #addTokens(org.apache.guacamole.net.auth.Connection, java.util.Map)}
|
||||
* instead.
|
||||
*
|
||||
* @param connection
|
||||
* The Connection on which connect() has been called.
|
||||
@@ -83,16 +86,45 @@ public class TokenInjectingUserContext extends DelegatingUserContext {
|
||||
* @return
|
||||
* The tokens which should be added to the in-progress call to
|
||||
* connect().
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the tokens applicable to the given connection cannot be
|
||||
* generated.
|
||||
*/
|
||||
protected Map<String, String> getTokens(Connection connection) {
|
||||
protected Map<String, String> getTokens(Connection connection)
|
||||
throws GuacamoleException {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tokens to an in-progress call to connect() for the given
|
||||
* Connection. If not overridden, this function will add the tokens
|
||||
* returned by {@link #getTokens(org.apache.guacamole.net.auth.Connection)}.
|
||||
*
|
||||
* @param connection
|
||||
* The Connection on which connect() has been called.
|
||||
*
|
||||
* @param tokens
|
||||
* A modifiable Map containing the tokens already supplied to
|
||||
* connect().
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the tokens applicable to the given connection cannot be
|
||||
* generated.
|
||||
*/
|
||||
protected void addTokens(Connection connection, Map<String, String> tokens)
|
||||
throws GuacamoleException {
|
||||
tokens.putAll(getTokens(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tokens which should be added to an in-progress call to
|
||||
* connect() for the given ConnectionGroup. If not overridden, this
|
||||
* function will return the tokens provided when this instance of
|
||||
* TokenInjectingUserContext was created.
|
||||
* TokenInjectingUserContext was created. If the values of existing tokens
|
||||
* need to be considered, implementations should override
|
||||
* {@link #addTokens(org.apache.guacamole.net.auth.ConnectionGroup, java.util.Map)}
|
||||
* instead.
|
||||
*
|
||||
* @param connectionGroup
|
||||
* The ConnectionGroup on which connect() has been called.
|
||||
@@ -100,11 +132,37 @@ public class TokenInjectingUserContext extends DelegatingUserContext {
|
||||
* @return
|
||||
* The tokens which should be added to the in-progress call to
|
||||
* connect().
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the tokens applicable to the given connection group cannot be
|
||||
* generated.
|
||||
*/
|
||||
protected Map<String, String> getTokens(ConnectionGroup connectionGroup) {
|
||||
protected Map<String, String> getTokens(ConnectionGroup connectionGroup)
|
||||
throws GuacamoleException {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tokens to an in-progress call to connect() for the given
|
||||
* ConnectionGroup. If not overridden, this function will add the tokens
|
||||
* returned by {@link #getTokens(org.apache.guacamole.net.auth.ConnectionGroup)}.
|
||||
*
|
||||
* @param connectionGroup
|
||||
* The ConnectionGroup on which connect() has been called.
|
||||
*
|
||||
* @param tokens
|
||||
* A modifiable Map containing the tokens already supplied to
|
||||
* connect().
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the tokens applicable to the given connection cannot be
|
||||
* generated.
|
||||
*/
|
||||
protected void addTokens(ConnectionGroup connectionGroup,
|
||||
Map<String, String> tokens) throws GuacamoleException {
|
||||
tokens.putAll(getTokens(connectionGroup));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Directory<ConnectionGroup> getConnectionGroupDirectory()
|
||||
throws GuacamoleException {
|
||||
@@ -112,7 +170,14 @@ public class TokenInjectingUserContext extends DelegatingUserContext {
|
||||
|
||||
@Override
|
||||
protected ConnectionGroup decorate(ConnectionGroup object) throws GuacamoleException {
|
||||
return new TokenInjectingConnectionGroup(object, getTokens(object));
|
||||
return new TokenInjectingConnectionGroup(object) {
|
||||
|
||||
@Override
|
||||
protected void addTokens(Map<String, String> tokens) throws GuacamoleException {
|
||||
TokenInjectingUserContext.this.addTokens(object, tokens);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,7 +195,14 @@ public class TokenInjectingUserContext extends DelegatingUserContext {
|
||||
|
||||
@Override
|
||||
protected Connection decorate(Connection object) throws GuacamoleException {
|
||||
return new TokenInjectingConnection(object, getTokens(object));
|
||||
return new TokenInjectingConnection(object) {
|
||||
|
||||
@Override
|
||||
protected void addTokens(Map<String, String> tokens) throws GuacamoleException {
|
||||
TokenInjectingUserContext.this.addTokens(object, tokens);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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));
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user