FunctionExprValidator validates whether function expressions in queries comply with established security policies.
When using expression extension features to register custom functions, you may need to restrict the usage scope of these functions to prevent abuse or security risks. FunctionExprValidator provides a mechanism to control which function expressions are allowed in queries.
public enum FunctionPolicy
{
AllowAll, // Allow all function expressions
AllowRegisted, // Only allow pre-registered function expressions
Disallow // Disallow any function expressions
}
| Policy | Description |
|---|---|
AllowAll |
Allow any function expression, including unregistered ones |
AllowRegisted |
Only allow functions registered via SqlBuilder.RegisterFunctionSqlHandler |
Disallow |
Disallow any function expressions |
FunctionExprValidator provides three preconfigured static instances:
// Allow all functions
var validatorAllowAll = FunctionExprValidator.AllowAll;
// Only allow registered functions
var validatorAllowRegisted = FunctionExprValidator.AllowRegisted;
// Disallow any functions
var validatorDisallow = FunctionExprValidator.Disallow;
var validator = new FunctionExprValidator(FunctionPolicy.AllowRegisted);
| Environment or Role | Recommended Policy |
|---|---|
| Local development / Internal tools | AllowAll |
| Production environment business queries | AllowRegisted |
| Restricted scenarios prohibiting custom functions | Disallow |
FunctionExprValidator inherits from ExprValidator. Single node validation uses Validate(node), while full expression tree validation more commonly uses VisitAll(expr):
using static LiteOrm.Common.Expr;
public override bool Validate(Expr node)
{
if (node == null) return true;
if (node is FunctionExpr funcExpr)
{
switch (FunctionPolicy)
{
case FunctionPolicy.AllowAll:
return true;
case FunctionPolicy.AllowRegisted:
return SqlBuilder.Instance.TryGetFunctionSqlHandler<SqlBuilder>(
funcExpr.FunctionName, out _);
case FunctionPolicy.Disallow:
return false;
}
}
return true;
}
In multi-tenant scenarios, restrict which functions different tenants can use:
public class UserQueryService
{
private readonly FunctionExprValidator _validator;
public UserQueryService(UserRole role)
{
_validator = role == UserRole.Admin
? FunctionExprValidator.AllowAll
: FunctionExprValidator.AllowRegisted;
}
public async Task<List<User>> SearchAsync(Expr query)
{
// Validate query expression
if (!_validator.VisitAll(query))
throw new InvalidOperationException("Query contains disallowed function expressions");
return await _userViewDAO.Search(query).ToListAsync();
}
}
public class SafeUserDAO : ObjectViewDAO<User>
{
private static readonly FunctionExprValidator Validator =
FunctionExprValidator.AllowRegisted;
public async Task<List<User>> SafeSearchAsync(Expr expr)
{
if (!Validator.VisitAll(expr))
throw new SecurityException("Expression validation failed");
return await Search(expr).ToListAsync();
}
}
Perform global validation before query execution:
public class QueryInterceptor
{
private readonly FunctionExprValidator _validator;
public QueryInterceptor(FunctionPolicy policy)
{
_validator = new FunctionExprValidator(policy);
}
public void Intercept(Expr query)
{
if (!_validator.VisitAll(query))
{
throw new UnauthorizedAccessException(
"Query contains unauthorized function expressions");
}
}
}
A typical flow: first register allowed extension functions, then use the validator as a final check before query execution.
MySqlBuilder.Instance.RegisterFunctionSqlHandler("DATE_FORMAT", ...);
var validator = FunctionExprValidator.AllowRegisted;
var expr = new FunctionExpr("DATE_FORMAT", ...);
if (!validator.VisitAll(expr))
throw new InvalidOperationException("Function not registered, execution disallowed");
FunctionExprValidator.AllowRegisted depends on functions registered via SqlBuilder.RegisterFunctionSqlHandler:
// Register function handlers
MySqlBuilder.Instance.RegisterFunctionSqlHandler("DATE_FORMAT", ...);
MySqlBuilder.Instance.RegisterFunctionSqlHandler("CONCAT", ...);
// AllowRegisted validator only allows these registered functions
var validator = FunctionExprValidator.AllowRegisted;
// DATE_FORMAT - allowed (registered)
var expr1 = new FunctionExpr("DATE_FORMAT", ...);
validator.VisitAll(expr1); // true
// CUSTOM_UNREGISTERED - rejected (not registered)
var expr2 = new FunctionExpr("CUSTOM_UNREGISTERED", ...);
validator.VisitAll(expr2); // false
validator.VisitAll(expr) in business scenarios; Validate(node) is more suitable for overriding when implementing validators.AllowRegisted policy in production environments.