parent
8ff7834ef8
commit
2f69176463
7
Elsa.sln
7
Elsa.sln
|
|
@ -245,6 +245,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "drop-ins", "drop-ins", "{6A
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleDropIn", "src\samples\drop-ins\SampleDropIn\SampleDropIn.csproj", "{E0567846-AC0E-4E09-96A1-EFED4B570A01}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.SasTokens", "src\modules\Elsa.SasTokens\Elsa.SasTokens.csproj", "{3095C95A-F1F9-487A-A983-1B100A33E4C7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -611,6 +613,10 @@ Global
|
|||
{E0567846-AC0E-4E09-96A1-EFED4B570A01}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E0567846-AC0E-4E09-96A1-EFED4B570A01}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E0567846-AC0E-4E09-96A1-EFED4B570A01}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3095C95A-F1F9-487A-A983-1B100A33E4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3095C95A-F1F9-487A-A983-1B100A33E4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3095C95A-F1F9-487A-A983-1B100A33E4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3095C95A-F1F9-487A-A983-1B100A33E4C7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
|
||||
|
|
@ -721,5 +727,6 @@ Global
|
|||
{4E7F15D1-729E-4DA7-992A-C3A2C6054B37} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
|
||||
{6AB6F2F7-CAC1-45C8-9D87-EEE56BC68601} = {155227F0-A33B-40AA-A4B4-06F813EB921B}
|
||||
{E0567846-AC0E-4E09-96A1-EFED4B570A01} = {6AB6F2F7-CAC1-45C8-9D87-EEE56BC68601}
|
||||
{3095C95A-F1F9-487A-A983-1B100A33E4C7} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -9,12 +9,15 @@ using Elsa.EntityFrameworkCore.Modules.Management;
|
|||
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Http.Handlers;
|
||||
using Elsa.Http.Options;
|
||||
using Elsa.MongoDb.Extensions;
|
||||
using Elsa.MongoDb.Modules.Identity;
|
||||
using Elsa.MongoDb.Modules.Management;
|
||||
using Elsa.MongoDb.Modules.Runtime;
|
||||
using Elsa.WorkflowServer.Web;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Proto.Persistence.Sqlite;
|
||||
using Proto.Persistence.SqlServer;
|
||||
|
||||
|
|
@ -141,7 +144,12 @@ services
|
|||
.UseRealTimeWorkflows()
|
||||
.UseJavaScript(js => js.JintOptions = options => options.AllowClrAccess = true)
|
||||
.UseLiquid(liquid => liquid.FluidOptions = options => options.Encoder = HtmlEncoder.Default)
|
||||
.UseHttp(http => http.HttpEndpointAuthorizationHandler = sp => sp.GetRequiredService<AllowAnonymousHttpEndpointAuthorizationHandler>())
|
||||
.UseHttp(http =>
|
||||
{
|
||||
http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options);
|
||||
http.HttpEndpointAuthorizationHandler = sp => sp.GetRequiredService<AllowAnonymousHttpEndpointAuthorizationHandler>();
|
||||
})
|
||||
.UseSasTokens(sas => sas.DataProtectionProvider = sp => DataProtectionProvider.Create("Elsa Workflows"))
|
||||
.UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options));
|
||||
|
||||
// Initialize drop-ins.
|
||||
|
|
@ -173,7 +181,8 @@ app.UseAuthentication();
|
|||
app.UseAuthorization();
|
||||
|
||||
// Elsa API endpoints for designer.
|
||||
app.UseWorkflowsApi();
|
||||
var routePrefix = app.Services.GetRequiredService<IOptions<HttpActivityOptions>>().Value.ApiRoutePrefix;
|
||||
app.UseWorkflowsApi(routePrefix);
|
||||
|
||||
// Captures unhandled exceptions and returns a JSON response.
|
||||
app.UseJsonSerializationErrorHandler();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,17 @@
|
|||
"Port": 2525,
|
||||
"DefaultSender": "noreply@crmservices.com"
|
||||
},
|
||||
"Http": {
|
||||
"BaseUrl": "https://localhost:5001",
|
||||
"BasePath": "/workflows",
|
||||
"ApiRoutePrefix": "elsa/api",
|
||||
"AvailableContentTypes": [
|
||||
"application/json",
|
||||
"application/xml",
|
||||
"text/plain",
|
||||
"text/html"
|
||||
]
|
||||
},
|
||||
"Identity": {
|
||||
"Tokens": {
|
||||
"SigningKey": "secret-signing-key",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,17 @@ using FastEndpoints;
|
|||
|
||||
namespace Elsa.Abstractions;
|
||||
|
||||
public abstract class ElsaEndpointWithoutRequest : EndpointWithoutRequest
|
||||
{
|
||||
protected void ConfigurePermissions(params string[] permissions)
|
||||
{
|
||||
if (!EndpointSecurityOptions.SecurityIsEnabled)
|
||||
AllowAnonymous();
|
||||
else
|
||||
Permissions(new[] { PermissionNames.All }.Concat(permissions).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ElsaEndpointWithoutRequest<TResponse> : EndpointWithoutRequest<TResponse> where TResponse : notnull
|
||||
{
|
||||
protected void ConfigurePermissions(params string[] permissions)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Liquid\Elsa.Liquid.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.SasTokens\Elsa.SasTokens.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
using Elsa.Expressions.Models;
|
||||
using Elsa.Http.Contracts;
|
||||
using Elsa.Http.Models;
|
||||
using Elsa.Http.Options;
|
||||
using Elsa.SasTokens.Contracts;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
internal static class ExpressionExecutionContextExtensions
|
||||
{
|
||||
public static string GenerateEventTriggerUrl(this ExpressionExecutionContext context, string eventName, TimeSpan lifetime)
|
||||
{
|
||||
var token = context.GenerateEventTriggerTokenInternal(eventName, lifetime);
|
||||
return context.GenerateEventTriggerUrlInternal(token);
|
||||
}
|
||||
|
||||
public static string GenerateEventTriggerUrl(this ExpressionExecutionContext context, string eventName, DateTimeOffset expiresAt)
|
||||
{
|
||||
var token = context.GenerateEventTriggerTokenInternal(eventName, expiresAt: expiresAt);
|
||||
return context.GenerateEventTriggerUrlInternal(token);
|
||||
}
|
||||
|
||||
public static string GenerateEventTriggerUrl(this ExpressionExecutionContext context, string eventName)
|
||||
{
|
||||
var token = context.GenerateEventTriggerTokenInternal(eventName);
|
||||
return context.GenerateEventTriggerUrlInternal(token);
|
||||
}
|
||||
|
||||
private static string GenerateEventTriggerUrlInternal(this ExpressionExecutionContext context, string token)
|
||||
{
|
||||
var options = context.GetRequiredService<IOptions<HttpActivityOptions>>().Value;
|
||||
var url = $"{options.ApiRoutePrefix}/events/trigger?t={token}";
|
||||
var absoluteUrlProvider = context.GetRequiredService<IAbsoluteUrlProvider>();
|
||||
return absoluteUrlProvider.ToAbsoluteUrl(url).ToString();
|
||||
}
|
||||
|
||||
private static string GenerateEventTriggerTokenInternal(this ExpressionExecutionContext context, string eventName, TimeSpan? lifetime = default, DateTimeOffset? expiresAt = default)
|
||||
{
|
||||
var workflowInstanceId = context.GetWorkflowExecutionContext().Id;
|
||||
var payload = new EventTokenPayload(eventName, workflowInstanceId);
|
||||
var tokenService = context.GetRequiredService<ITokenService>();
|
||||
|
||||
return lifetime != null
|
||||
? tokenService.CreateToken(payload, lifetime.Value)
|
||||
: expiresAt != null
|
||||
? tokenService.CreateToken(payload, expiresAt.Value)
|
||||
: tokenService.CreateToken(payload);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
using Elsa.Http.Contracts;
|
||||
using Elsa.Http.Models;
|
||||
using Elsa.SasTokens.Contracts;
|
||||
using Elsa.Workflows.Core;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
internal static class ActivityContextExtensions
|
||||
internal static class HttpActivityExecutionContextExtensions
|
||||
{
|
||||
public static async Task<object?> ParseContentAsync(this ActivityExecutionContext context, Stream content, string contentType, Type? returnType, CancellationToken cancellationToken)
|
||||
{
|
||||
8
src/modules/Elsa.Http/Models/EventTokenPayload.cs
Normal file
8
src/modules/Elsa.Http/Models/EventTokenPayload.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace Elsa.Http.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the payload of an event, serialized as a secured token.
|
||||
/// </summary>
|
||||
/// <param name="EventName">The name of the event.</param>
|
||||
/// <param name="WorkflowInstanceId">The ID of the workflow instance to trigger with the event.</param>
|
||||
public record EventTokenPayload(string EventName, string WorkflowInstanceId);
|
||||
|
|
@ -17,10 +17,15 @@ public class HttpActivityOptions
|
|||
/// </summary>
|
||||
public Uri BaseUrl { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The prefix used for API routes.
|
||||
/// </summary>
|
||||
public string ApiRoutePrefix { get; set; } = "elsa/api";
|
||||
|
||||
/// <summary>
|
||||
/// A configurable set of available content types available from the <see cref="WriteHttpResponse"/> activity.
|
||||
/// </summary>
|
||||
public ISet<string> AvailableContentTypes { get; set; } = new HashSet<string>
|
||||
public ISet<string> AvailableContentTypes { get; set; } = new SortedSet<string>
|
||||
{
|
||||
"application/json",
|
||||
"application/xml",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Http.Models;
|
||||
using Elsa.JavaScript.Notifications;
|
||||
using Elsa.JavaScript.TypeDefinitions.Builders;
|
||||
using Elsa.JavaScript.TypeDefinitions.Contracts;
|
||||
using Elsa.JavaScript.TypeDefinitions.Models;
|
||||
using Elsa.Mediator.Contracts;
|
||||
|
|
@ -12,7 +13,7 @@ namespace Elsa.Http.Scripting.JavaScript;
|
|||
/// Configures the JavaScript engine with additional .NET types that can be instantiated.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public class HttpJavaScriptHandler : INotificationHandler<EvaluatingJavaScript>, ITypeDefinitionProvider
|
||||
public class HttpJavaScriptHandler : INotificationHandler<EvaluatingJavaScript>, ITypeDefinitionProvider, IFunctionDefinitionProvider
|
||||
{
|
||||
private readonly ITypeDescriber _typeDescriber;
|
||||
|
||||
|
|
@ -23,26 +24,59 @@ public class HttpJavaScriptHandler : INotificationHandler<EvaluatingJavaScript>,
|
|||
{
|
||||
_typeDescriber = typeDescriber;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task HandleAsync(EvaluatingJavaScript notification, CancellationToken cancellationToken)
|
||||
Task INotificationHandler<EvaluatingJavaScript>.HandleAsync(EvaluatingJavaScript notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var engine = notification.Engine;
|
||||
engine.RegisterType<HttpRequestHeaders>();
|
||||
engine.RegisterType<Downloadable>();
|
||||
|
||||
var activityExecutionContext = notification.Context;
|
||||
|
||||
engine.SetValue("createEventTriggerUrl", (Func<string, object?, string>)((eventName, lifetimeOrExpiryDate) =>
|
||||
{
|
||||
return lifetimeOrExpiryDate switch
|
||||
{
|
||||
TimeSpan lifetime => activityExecutionContext.GenerateEventTriggerUrl(eventName, lifetime),
|
||||
DateTimeOffset expiryDate => activityExecutionContext.GenerateEventTriggerUrl(eventName, expiryDate),
|
||||
_ => activityExecutionContext.GenerateEventTriggerUrl(eventName)
|
||||
};
|
||||
}));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<TypeDefinition>> GetTypeDefinitionsAsync(TypeDefinitionContext context)
|
||||
ValueTask<IEnumerable<TypeDefinition>> ITypeDefinitionProvider.GetTypeDefinitionsAsync(TypeDefinitionContext context)
|
||||
{
|
||||
var definitions = GetTypeDefinitions(context);
|
||||
return new(definitions);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
ValueTask<IEnumerable<FunctionDefinition>> IFunctionDefinitionProvider.GetFunctionDefinitionsAsync(TypeDefinitionContext context)
|
||||
{
|
||||
var definitions = GetFunctionDefinitions(context);
|
||||
return new(definitions);
|
||||
}
|
||||
|
||||
private IEnumerable<TypeDefinition> GetTypeDefinitions(TypeDefinitionContext context)
|
||||
{
|
||||
yield return _typeDescriber.DescribeType(typeof(HttpRequestHeaders));
|
||||
yield return _typeDescriber.DescribeType(typeof(Downloadable));
|
||||
}
|
||||
|
||||
private IEnumerable<FunctionDefinition> GetFunctionDefinitions(TypeDefinitionContext context)
|
||||
{
|
||||
yield return CreateFunctionDefinition(function => function.Name("createEventTriggerUrl").ReturnType("string").Parameter("eventName", "string"));
|
||||
yield return CreateFunctionDefinition(function => function.Name("createEventTriggerUrl").ReturnType("string").Parameter("eventName", "string").Parameter("lifetime", "TimeSpan"));
|
||||
yield return CreateFunctionDefinition(function => function.Name("createEventTriggerUrl").ReturnType("string").Parameter("eventName", "string").Parameter("expiresAt", "DateTimeOffset"));
|
||||
}
|
||||
|
||||
private FunctionDefinition CreateFunctionDefinition(Action<FunctionDefinitionBuilder> setup)
|
||||
{
|
||||
var builder = new FunctionDefinitionBuilder();
|
||||
setup(builder);
|
||||
return builder.BuildFunctionDefinition();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,11 +4,17 @@ using Microsoft.Extensions.Options;
|
|||
|
||||
namespace Elsa.Http.Services;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class DefaultAbsoluteUrlProvider : IAbsoluteUrlProvider
|
||||
{
|
||||
private readonly IOptions<HttpActivityOptions> _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultAbsoluteUrlProvider"/> class.
|
||||
/// </summary>
|
||||
public DefaultAbsoluteUrlProvider(IOptions<HttpActivityOptions> options) => _options = options;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri ToAbsoluteUrl(string relativePath)
|
||||
{
|
||||
var baseUrl = _options.Value.BaseUrl;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
|
||||
namespace Elsa.SasTokens.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// A service that can create and decrypt SAS (Shared Access Signature) tokens using the <see cref="Microsoft.AspNetCore.DataProtection.IDataProtector"/> service.
|
||||
/// </summary>
|
||||
public class DataProtectorTokenService : ITokenService
|
||||
{
|
||||
private readonly IDataProtector _dataProtector;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataProtectorTokenService"/> class.
|
||||
/// </summary>
|
||||
public DataProtectorTokenService(IDataProtectionProvider dataProtector)
|
||||
{
|
||||
_dataProtector = dataProtector.CreateProtector("Elsa Tokens");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string CreateToken<T>(T payload, TimeSpan lifetime)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
return _dataProtector.ToTimeLimitedDataProtector().Protect(json, lifetime);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string CreateToken<T>(T payload, DateTimeOffset expiresAt)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
return _dataProtector.ToTimeLimitedDataProtector().Protect(json, expiresAt);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string CreateToken<T>(T payload)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
return _dataProtector.Protect(json);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryDecryptToken<T>(string token, out T payload)
|
||||
{
|
||||
payload = default!;
|
||||
|
||||
try
|
||||
{
|
||||
payload = DecryptToken<T>(token);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T DecryptToken<T>(string token)
|
||||
{
|
||||
var json = _dataProtector.Unprotect(token);
|
||||
return JsonSerializer.Deserialize<T>(json)!;
|
||||
}
|
||||
}
|
||||
32
src/modules/Elsa.SasTokens/Contracts/ITokenService.cs
Normal file
32
src/modules/Elsa.SasTokens/Contracts/ITokenService.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
namespace Elsa.SasTokens.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// A service that can create and decrypt SAS (Shared Access Signature) tokens.
|
||||
/// </summary>
|
||||
public interface ITokenService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a SAS (Shared Access Signature) token containing the specified data.
|
||||
/// </summary>
|
||||
string CreateToken<T>(T payload, TimeSpan lifetime);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SAS (Shared Access Signature) token containing the specified data.
|
||||
/// </summary>
|
||||
string CreateToken<T>(T payload, DateTimeOffset expiresAt);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SAS (Shared Access Signature) token containing the specified data.
|
||||
/// </summary>
|
||||
string CreateToken<T>(T payload);
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts the specified SAS token.
|
||||
/// </summary>
|
||||
T DecryptToken<T>(string token);
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts the specified SAS token.
|
||||
/// </summary>
|
||||
bool TryDecryptToken<T>(string token, out T payload);
|
||||
}
|
||||
22
src/modules/Elsa.SasTokens/Elsa.SasTokens.csproj
Normal file
22
src/modules/Elsa.SasTokens/Elsa.SasTokens.csproj
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\common.props"/>
|
||||
<Import Project="..\..\..\configureawait.props"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<Description>
|
||||
Provides services to geenrate SAS tokens.
|
||||
</Description>
|
||||
<PackageTags>elsa module security sas tokens</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0' Or '$(TargetFramework)' == 'net7.0'">
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\common\Elsa.Features\Elsa.Features.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
20
src/modules/Elsa.SasTokens/Extensions/ModuleExtensions.cs
Normal file
20
src/modules/Elsa.SasTokens/Extensions/ModuleExtensions.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using Elsa.Features.Services;
|
||||
using Elsa.SasTokens.Features;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extensions to install the <see cref="SasTokens"/> feature.
|
||||
/// </summary>
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Install the <see cref="SasTokens"/> feature.
|
||||
/// </summary>
|
||||
public static IModule UseSasTokens(this IModule module, Action<SasTokensFeature>? configure = default)
|
||||
{
|
||||
module.Configure(configure);
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using Elsa.SasTokens.Contracts;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.SasTokens.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="IServiceCollection"/> interface.
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the SAS tokens module to the service collection.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddSasTokens(this IServiceCollection services, Func<IServiceProvider, IDataProtectionProvider> dataProtectionProvider)
|
||||
{
|
||||
services.AddSingleton<ITokenService>(sp =>
|
||||
{
|
||||
var protectionProvider = dataProtectionProvider(sp);
|
||||
return new DataProtectorTokenService(protectionProvider);
|
||||
});
|
||||
return services;
|
||||
}
|
||||
}
|
||||
28
src/modules/Elsa.SasTokens/Features/SasTokensFeature.cs
Normal file
28
src/modules/Elsa.SasTokens/Features/SasTokensFeature.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.SasTokens.Extensions;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
|
||||
namespace Elsa.SasTokens.Features;
|
||||
|
||||
/// <summary>
|
||||
/// Adds the SAS tokens feature to the workflow runtime.
|
||||
/// </summary>
|
||||
public class SasTokensFeature : FeatureBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public SasTokensFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the data protection provider used to create the <see cref="Microsoft.AspNetCore.DataProtection.IDataProtector"/> used to encrypt and decrypt the SAS tokens.
|
||||
/// </summary>
|
||||
public Func<IServiceProvider, IDataProtectionProvider> DataProtectionProvider { get; set; } = _ => Microsoft.AspNetCore.DataProtection.DataProtectionProvider.Create("Elsa Workflows");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
Services.AddSasTokens(DataProtectionProvider);
|
||||
}
|
||||
}
|
||||
3
src/modules/Elsa.SasTokens/FodyWeavers.xml
Normal file
3
src/modules/Elsa.SasTokens/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Http.Contracts;
|
||||
using Elsa.Workflows.Api.Endpoints.Events.Trigger;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Workflows.Api.Endpoints.Events.Trigger;
|
||||
namespace Elsa.Workflows.Api.Endpoints.Events.TriggerAuthenticated;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers all workflows that are waiting for the specified event.
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Http.Contracts;
|
||||
using Elsa.Http.Models;
|
||||
using Elsa.SasTokens.Contracts;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Workflows.Api.Endpoints.Events.TriggerPublic;
|
||||
|
||||
/// <summary>
|
||||
/// Resumes a workflow instance blocked by a specified event encoded in the provided SAS token.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
internal class Trigger : ElsaEndpointWithoutRequest
|
||||
{
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
private readonly IHttpBookmarkProcessor _httpBookmarkProcessor;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Trigger(ITokenService tokenService, IEventPublisher eventPublisher, IHttpBookmarkProcessor httpBookmarkProcessor)
|
||||
{
|
||||
_tokenService = tokenService;
|
||||
_eventPublisher = eventPublisher;
|
||||
_httpBookmarkProcessor = httpBookmarkProcessor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/events/trigger");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task HandleAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var token = Query<string>("t")!;
|
||||
|
||||
if (!_tokenService.TryDecryptToken<EventTokenPayload>(token, out var payload))
|
||||
{
|
||||
AddError("Invalid token.");
|
||||
await SendErrorsAsync(cancellation: cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
var eventName = payload.EventName;
|
||||
var workflowInstanceId = payload.WorkflowInstanceId;
|
||||
var results = await _eventPublisher.PublishAsync(eventName, workflowInstanceId: workflowInstanceId, cancellationToken: cancellationToken);
|
||||
|
||||
await _httpBookmarkProcessor.ProcessBookmarks(results, cancellationToken: cancellationToken);
|
||||
|
||||
if (!HttpContext.Response.HasStarted)
|
||||
await SendOkAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue