Skip to content

Commit

Permalink
Change how its decided whether to store or expose activity properties
Browse files Browse the repository at this point in the history
  • Loading branch information
tanelkuhi committed Sep 29, 2023
1 parent b2f48b4 commit e5d3ff8
Show file tree
Hide file tree
Showing 18 changed files with 100 additions and 76 deletions.
21 changes: 21 additions & 0 deletions src/core/Elsa.Abstractions/Events/ValidatePropertyExposure.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Elsa.Services.Models;
using MediatR;

namespace Elsa.Events;

public class ValidatePropertyExposure : INotification
{
public IWorkflowBlueprint WorkflowBlueprint { get; }
public string ActivityId { get; }
public string PropertyName { get; }

public ValidatePropertyExposure(IWorkflowBlueprint workflowBlueprint, string activityId, string propertyName)
{
WorkflowBlueprint = workflowBlueprint;
ActivityId = activityId;
PropertyName = propertyName;
}

public bool CanExposeProperty { get; private set; } = true;
public void PreventPropertyExposure() => CanExposeProperty = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,5 @@ public interface IExpressionEvaluator
Task<T?> EvaluateAsync<T>(string? expression, string syntax, ActivityExecutionContext context, CancellationToken cancellationToken = default);
Task<Result<object?>> TryEvaluateAsync(string? expression, string syntax, Type returnType, ActivityExecutionContext context, CancellationToken cancellationToken = default);
Task<Result<T?>> TryEvaluateAsync<T>(string? expression, string syntax, ActivityExecutionContext context, CancellationToken cancellationToken = default);
Task<bool> IsNonStorableExpression(string expression, string syntax, CancellationToken cancellationToken);
}
}
2 changes: 0 additions & 2 deletions src/core/Elsa.Abstractions/Expressions/IExpressionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,5 @@ public interface IExpressionHandler
Type returnType,
ActivityExecutionContext context,
CancellationToken cancellationToken);

Task<bool> IsNonStorableExpression(string expression, CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@ public interface IActivityPropertyValueProvider
{
public string? RawValue { get; }
ValueTask<object?> GetValueAsync(ActivityExecutionContext context, CancellationToken cancellationToken = default);
ValueTask<bool> IsNonStorablePropertyValue(ActivityExecutionContext context, CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ public SwitchHandler(IContentSerializer contentSerializer)
return evaluatedCases;
}

public Task<bool> IsNonStorableExpression(string expression, CancellationToken cancellationToken)
{
return Task.FromResult(false);
}

private async IAsyncEnumerable<SwitchCase> EvaluateCasesAsync(IEnumerable<SwitchCaseModel> caseModels, ActivityExecutionContext context, [EnumeratorCancellation] CancellationToken cancellationToken)
{
var validCaseModels = caseModels.Where(x => x.Expressions != null && !string.IsNullOrWhiteSpace(x.Syntax) && x.Expressions.ContainsKey(x.Syntax)).ToList();
Expand Down
6 changes: 0 additions & 6 deletions src/core/Elsa.Core/Expressions/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,6 @@ public ExpressionEvaluator(IEnumerable<IExpressionHandler> evaluators, ILogger<E
}
}

public async Task<bool> IsNonStorableExpression(string expression, string syntax, CancellationToken cancellationToken)
{
var evaluator = _evaluators[syntax];
return await evaluator.IsNonStorableExpression(expression, cancellationToken);
}

public async Task<T?> EvaluateAsync<T>(string? expression, string syntax, ActivityExecutionContext context, CancellationToken cancellationToken = default) =>
(T) (await EvaluateAsync(expression, syntax, typeof(T), context, cancellationToken))!;

Expand Down
5 changes: 0 additions & 5 deletions src/core/Elsa.Core/Expressions/JsonHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,5 @@ public class JsonHandler : IExpressionHandler
var value = JsonConvert.DeserializeObject(expression, returnType);
return Task.FromResult(value)!;
}

public Task<bool> IsNonStorableExpression(string expression, CancellationToken cancellationToken)
{
return Task.FromResult(false);
}
}
}
5 changes: 0 additions & 5 deletions src/core/Elsa.Core/Expressions/LiteralHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,5 @@ public class LiteralHandler : IExpressionHandler
ActivityExecutionContext context,
CancellationToken cancellationToken) =>
Task.FromResult(expression.Parse(returnType));

public Task<bool> IsNonStorableExpression(string expression, CancellationToken cancellationToken)
{
return Task.FromResult(false);
}
}
}
5 changes: 0 additions & 5 deletions src/core/Elsa.Core/Expressions/VariableHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,5 @@ public class VariableHandler : IExpressionHandler
var result = context.GetVariable(expression);
return Task.FromResult(result);
}

public Task<bool> IsNonStorableExpression(string expression, CancellationToken cancellationToken)
{
return Task.FromResult(false);
}
}
}
14 changes: 9 additions & 5 deletions src/core/Elsa.Core/Handlers/PersistActivityPropertyState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ namespace Elsa.Handlers
public class PersistActivityPropertyState : INotificationHandler<ActivityExecuted>
{
private readonly IWorkflowStorageService _workflowStorageService;
private readonly IMediator _mediator;

public PersistActivityPropertyState(IWorkflowStorageService workflowStorageService)
public PersistActivityPropertyState(IWorkflowStorageService workflowStorageService, IMediator mediator)
{
_workflowStorageService = workflowStorageService;
_mediator = mediator;
}

public async Task Handle(ActivityExecuted notification, CancellationToken cancellationToken)
Expand All @@ -35,8 +37,9 @@ public async Task Handle(ActivityExecuted notification, CancellationToken cancel
// Persist input properties.
foreach (var property in inputProperties)
{
var propProvider = notification.WorkflowExecutionContext.WorkflowBlueprint.ActivityPropertyProviders.GetProvider(activity.Id, property.Name);
if (propProvider != null && await propProvider.IsNonStorablePropertyValue(notification.ActivityExecutionContext, cancellationToken))
var validatePropertyExposure = new ValidatePropertyExposure(activityExecutionContext.WorkflowExecutionContext.WorkflowBlueprint, activity.Id, property.Name);
await _mediator.Publish(validatePropertyExposure, cancellationToken);
if (!validatePropertyExposure.CanExposeProperty)
{
continue;
}
Expand All @@ -50,8 +53,9 @@ public async Task Handle(ActivityExecuted notification, CancellationToken cancel
// Persist output properties.
foreach (var property in outputProperties)
{
var propProvider = notification.WorkflowExecutionContext.WorkflowBlueprint.ActivityPropertyProviders.GetProvider(activity.Id, property.Name);
if (propProvider != null && await propProvider.IsNonStorablePropertyValue(notification.ActivityExecutionContext, cancellationToken))
var validatePropertyExposure = new ValidatePropertyExposure(activityExecutionContext.WorkflowExecutionContext.WorkflowBlueprint, activity.Id, property.Name);
await _mediator.Publish(validatePropertyExposure, cancellationToken);
if (!validatePropertyExposure.CanExposeProperty)
{
continue;
}
Expand Down
17 changes: 13 additions & 4 deletions src/core/Elsa.Core/Services/Workflows/ActivityActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,27 @@
using System.Threading.Tasks;
using Elsa.Activities.ControlFlow;
using Elsa.Attributes;
using Elsa.Events;
using Elsa.Options;
using Elsa.Providers.WorkflowStorage;
using Elsa.Services.Models;
using Elsa.Services.WorkflowStorage;
using MediatR;
using Microsoft.Extensions.DependencyInjection;

namespace Elsa.Services.Workflows
{
public class ActivityActivator : IActivityActivator
{
private readonly ElsaOptions _elsaOptions;
private readonly IWorkflowStorageService _workflowStorageService;
private readonly IServiceProvider _serviceProvider;

public ActivityActivator(ElsaOptions options, IWorkflowStorageService workflowStorageService)
public ActivityActivator(ElsaOptions options, IWorkflowStorageService workflowStorageService, IServiceProvider serviceProvider)
{
_elsaOptions = options;
_workflowStorageService = workflowStorageService;
_serviceProvider = serviceProvider;
}

public async Task<IActivity> ActivateActivityAsync(ActivityExecutionContext context, Type type, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -87,6 +92,9 @@ private async ValueTask StoreAppliedValuesAsync(ActivityExecutionContext context

private async ValueTask StoreAppliedObjectValuesAsync(ActivityExecutionContext context, IActivity activity, object nestedInstance, CancellationToken cancellationToken, string? parentName = null)
{
using var scope = _serviceProvider.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();

var properties = nestedInstance.GetType().GetProperties().Where(IsActivityProperty).ToList();
var nestedProperties = nestedInstance.GetType().GetProperties().Where(IsActivityObjectProperty).ToList();
var propertyStorageProviderDictionary = context.ActivityBlueprint.PropertyStorageProviders;
Expand All @@ -96,12 +104,13 @@ private async ValueTask StoreAppliedObjectValuesAsync(ActivityExecutionContext c
{
var propertyName = parentName == null ? property.Name : $"{parentName}_{property.Name}";

var propProvider = context.WorkflowExecutionContext.WorkflowBlueprint.ActivityPropertyProviders.GetProvider(activity.Id, propertyName);
if (propProvider != null && await propProvider.IsNonStorablePropertyValue(context, cancellationToken))
var validatePropertyExposure = new ValidatePropertyExposure(context.WorkflowExecutionContext.WorkflowBlueprint, activity.Id, propertyName);
await mediator.Publish(validatePropertyExposure, cancellationToken);
if (!validatePropertyExposure.CanExposeProperty)
{
continue;
}

var value = property.GetValue(nestedInstance);
var attr = property.GetCustomAttributes<ActivityPropertyAttributeBase>().First();
var providerName = propertyStorageProviderDictionary.GetItem(propertyName) ?? attr.DefaultWorkflowStorageProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,5 @@ public ExpressionActivityPropertyValueProvider(string? expression, string syntax
var evaluator = context.GetService<IExpressionEvaluator>();
return await evaluator.EvaluateAsync(Expression, Syntax, Type, context, cancellationToken);
}

public async ValueTask<bool> IsNonStorablePropertyValue(ActivityExecutionContext context, CancellationToken cancellationToken = default)
{
if (Expression == null)
return false;

var evaluator = context.GetService<IExpressionEvaluator>();
return await evaluator.IsNonStorableExpression(Expression, Syntax, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,5 @@ public SecretsExpressionHandler(ISecretsProvider secretsProvider)

return await _secretsProvider.GetSecretByNameAsync(expression);
}

public async Task<bool> IsNonStorableExpression(string expression, CancellationToken cancellationToken)
{
Match m;
if ((m = fullyQualifiedName.Match(expression)).Success)
return await _secretsProvider.IsSecretValueSensitiveData(m.Groups["Type"].Value, m.Groups["Name"].Value);

return await _secretsProvider.IsSecretValueSensitiveData(expression);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Events;
using Elsa.Secrets.Providers;
using Elsa.Services.Workflows;
using MediatR;

namespace Elsa.Secrets.Handlers;

public class ValidatePropertyExposureHandler : INotificationHandler<ValidatePropertyExposure>
{
private readonly Regex _fullyQualifiedName = new Regex("(?<Type>[^:]+):(?<Name>.*)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
private readonly ISecretsProvider _secretsProvider;

public ValidatePropertyExposureHandler(ISecretsProvider secretsProvider)
{
_secretsProvider = secretsProvider;
}

public async Task Handle(ValidatePropertyExposure notification, CancellationToken cancellationToken)
{
var propProvider = notification.WorkflowBlueprint.ActivityPropertyProviders.GetProvider(notification.ActivityId, notification.PropertyName);
var expressionProvider = propProvider as ExpressionActivityPropertyValueProvider;

if (expressionProvider is not { Syntax: "Secret" })
{
return;
}

Match m;
if ((m = _fullyQualifiedName.Match(expressionProvider.Expression)).Success)
{
if (await _secretsProvider.IsSecretValueSensitiveData(m.Groups["Type"].Value, m.Groups["Name"].Value))
{
notification.PreventPropertyExposure();
}
}
else
{
if (await _secretsProvider.IsSecretValueSensitiveData(expressionProvider.Expression))
{
notification.PreventPropertyExposure();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,5 @@ public JavaScriptExpressionHandler(IJavaScriptService javaScriptService)

public async Task<object?> EvaluateAsync(string expression, Type returnType, ActivityExecutionContext context, CancellationToken cancellationToken) =>
await _javaScriptService.EvaluateAsync(expression, returnType, context, cancellationToken: cancellationToken);

public Task<bool> IsNonStorableExpression(string expression, CancellationToken cancellationToken)
{
return Task.FromResult(false);
}
}
}
5 changes: 0 additions & 5 deletions src/scripting/Elsa.Scripting.Liquid/Services/LiquidHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ public LiquidHandler(ILiquidTemplateManager liquidTemplateManager, IMediator med
return string.IsNullOrWhiteSpace(result) ? default : result.Parse(returnType);
}

public Task<bool> IsNonStorableExpression(string expression, CancellationToken cancellationToken)
{
return Task.FromResult(false);
}

private async Task<TemplateContext> CreateTemplateContextAsync(ActivityExecutionContext activityExecutionContext, CancellationToken cancellationToken)
{
var context = new TemplateContext(activityExecutionContext, new TemplateOptions());
Expand Down
5 changes: 0 additions & 5 deletions src/scripting/Elsa.Scripting.Sql/Expression/SqlHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,6 @@ public class SqlHandler : IExpressionHandler
return Task.FromResult(expression.Parse(returnType));
}

public Task<bool> IsNonStorableExpression(string expression, CancellationToken cancellationToken)
{
return Task.FromResult(false);
}

private bool IsNumber(object value)
{
return value is sbyte
Expand Down
14 changes: 10 additions & 4 deletions src/server/Elsa.Server.Api/Services/WorkflowBlueprintMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Elsa.Events;
using Elsa.Exceptions;
using Elsa.Metadata;
using Elsa.Models;
using Elsa.Server.Api.Endpoints.WorkflowRegistry;
using Elsa.Server.Api.Mapping;
using Elsa.Services;
using Elsa.Services.Models;
using MediatR;
using Microsoft.Extensions.DependencyInjection;

namespace Elsa.Server.Api.Services
Expand All @@ -20,13 +22,15 @@ public class WorkflowBlueprintMapper : IWorkflowBlueprintMapper
private readonly IActivityTypeService _activityTypeService;
private readonly IMapper _mapper;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IMediator _mediator;

public WorkflowBlueprintMapper(IWorkflowBlueprintReflector workflowBlueprintReflector, IActivityTypeService activityTypeService, IMapper mapper, IServiceScopeFactory serviceScopeFactory)
public WorkflowBlueprintMapper(IWorkflowBlueprintReflector workflowBlueprintReflector, IActivityTypeService activityTypeService, IMapper mapper, IServiceScopeFactory serviceScopeFactory, IMediator mediator)
{
_workflowBlueprintReflector = workflowBlueprintReflector;
_activityTypeService = activityTypeService;
_mapper = mapper;
_serviceScopeFactory = serviceScopeFactory;
_mediator = mediator;
}

public async ValueTask<WorkflowBlueprintModel> MapAsync(IWorkflowBlueprint workflowBlueprint, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -70,12 +74,14 @@ public async ValueTask<WorkflowBlueprintModel> MapAsync(IWorkflowBlueprint workf
return (inputProperties, outputProperties);
}

private static async Task<object?> GetPropertyValueAsync(IWorkflowBlueprint workflowBlueprint, IActivityBlueprintWrapper activityBlueprintWrapper, ActivityInputDescriptor propertyDescriptor, CancellationToken cancellationToken)
private async Task<object?> GetPropertyValueAsync(IWorkflowBlueprint workflowBlueprint, IActivityBlueprintWrapper activityBlueprintWrapper, ActivityInputDescriptor propertyDescriptor, CancellationToken cancellationToken)
{
if (propertyDescriptor.IsDesignerCritical)
{
var propProvider = workflowBlueprint.ActivityPropertyProviders.GetProvider(activityBlueprintWrapper.ActivityBlueprint.Id, propertyDescriptor.Name);
if (propProvider != null && !(await propProvider.IsNonStorablePropertyValue(activityBlueprintWrapper.ActivityExecutionContext, cancellationToken)))
var validatePropertyExposure = new ValidatePropertyExposure(workflowBlueprint, activityBlueprintWrapper.ActivityBlueprint.Id, propertyDescriptor.Name);
await _mediator.Publish(validatePropertyExposure, cancellationToken);

if (validatePropertyExposure.CanExposeProperty)
{
try
{
Expand Down

0 comments on commit e5d3ff8

Please sign in to comment.