Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Work queue api 2022 4 #1568

Merged
merged 9 commits into from
May 19, 2023
2 changes: 1 addition & 1 deletion backend/Origam.Server/Common/SessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public SessionStore GetSession(Guid sessionFormIdentifier, bool rootSession)
.GetOrAdd(
sessionFormIdentifier,
guid => throw new SessionExpiredException()
);
);

if (ss == null)
{
Expand Down
88 changes: 88 additions & 0 deletions backend/Origam.Server/Controller/WorkQueueController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#region license

/*
Copyright 2005 - 2023 Advantage Solutions, s. r. o.

This file is part of ORIGAM (http://www.origam.org).

ORIGAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

ORIGAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ORIGAM. If not, see <http://www.gnu.org/licenses/>.
*/

#endregion

using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Origam.Service.Core;
using Origam.Workbench.Services;

namespace Origam.Server.Controller;

[ApiController]
public class WorkQueueController : ControllerBase
{
private static readonly log4net.ILog log
= log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

[HttpPost]
[Route("workQueue/{workQueueCode}/{commandText}")]
public IActionResult CreateSessionAsync(string workQueueCode,
string commandText, [FromQuery] [Required] Guid workQueueEntryId)
{
if (log.IsDebugEnabled)
{
log.Debug("Processing: " + HttpContext.Request.GetDisplayUrl());
}
HttpContext.Response.ContentType = "application/json";
try
{
IWorkQueueService workQueueService =
ServiceManager.Services.GetService<IWorkQueueService>();
workQueueService.HandleAction(workQueueCode, commandText,
workQueueEntryId);
return Ok();
}
catch (Exception ex)
{
if (log.IsErrorEnabled)
{
log.Error(ex.Message, ex);
}
string output;
if (ex is RuleException ruleException)
{
output = String.Format(
"{{\"Message\" : {0}, \"RuleResult\" : {1}}}",
JsonConvert.SerializeObject(ruleException.Message),
JsonConvert.SerializeObject(ruleException.RuleResult));
}
else if (ex is ArgumentOutOfRangeException argumentException)
{
output = String.Format(
"{{\"Message\" : {0}, \"ParamName\" : {1}, \"ActualValue\" : {2}}}",
JsonConvert.SerializeObject(argumentException.Message),
JsonConvert.SerializeObject(argumentException.ParamName),
JsonConvert.SerializeObject(argumentException.ActualValue));
}
else
{
output = JsonConvert.SerializeObject(ex);
}
return BadRequest(output);
}
}
}
17 changes: 16 additions & 1 deletion backend/Origam.Server/IApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,21 @@ public static void UseUserApi(this IApplicationBuilder app, StartUpConfiguration
});
apiBranch.UseMiddleware<UserApiMiddleware>();
});
}

public static void UseWorkQueueApi(this IApplicationBuilder app)
{
app.MapWhen(
context => context.Request.Path.ToString().StartsWith("/workQueue"),
apiBranch =>
{
apiBranch.UseMiddleware<UserApiTokenAuthenticationMiddleware>();
apiBranch.UseMvc(routes =>
{
routes.MapRoute("default", "{controller}/{action=Index}/{id?}");
});
}
);
}

private static bool IsRestrictedUserApiRoute(
Expand Down Expand Up @@ -145,4 +160,4 @@ private static bool IsSoapApiRoute(HttpContext context)
return context.Request.Path.ToString().StartsWith("/soap");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#region license
/*
Copyright 2005 - 2023 Advantage Solutions, s. r. o.

This file is part of ORIGAM (http://www.origam.org).

ORIGAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

ORIGAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ORIGAM. If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityServer4;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Extensions.DependencyInjection;

namespace Origam.Server.Middleware;

/// <summary>
/// Middleware that performs authentication based on the IdentityServerAccessToken.
/// Based on AuthenticationMiddleware
/// https://github.com/dotnet/dotnet/blob/0fa4e2051eede834ddc4da42848a835aafc2f3da/src/aspnetcore/src/Security/Authentication/Core/src/AuthenticationMiddleware.cs
/// </summary>
public class UserApiTokenAuthenticationMiddleware
{
private readonly RequestDelegate _next;

public UserApiTokenAuthenticationMiddleware(RequestDelegate next,
IAuthenticationSchemeProvider schemes)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
Schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
}

public IAuthenticationSchemeProvider Schemes { get; set; }

public async Task Invoke(HttpContext context)
{
context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
});

// Give any IAuthenticationRequestHandler schemes a chance to handle the request
var handlers = context.RequestServices
.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
var handler =
await handlers.GetHandlerAsync(context, scheme.Name) as
IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
{
return;
}
}

// Using the IdentityServerConstants.LocalApi.AuthenticationScheme here
// causes the authentication to use the IdentityServerAccessToken.
var result = await context.AuthenticateAsync(IdentityServerConstants.LocalApi.AuthenticationScheme);
if (result?.Principal != null)
{
context.User = result.Principal;
}

if (result?.Succeeded ?? false)
{
var authFeatures = new OrigamAuthenticationFeatures(result);
context.Features.Set<IHttpAuthenticationFeature>(authFeatures);
context.Features.Set<IAuthenticateResultFeature>(authFeatures);
}
else
{
context.Response.StatusCode = 401;
return;
}
await _next(context);
}
}

class OrigamAuthenticationFeatures : IAuthenticateResultFeature, IHttpAuthenticationFeature
{
private ClaimsPrincipal? _user;
private AuthenticateResult? _result;

public OrigamAuthenticationFeatures(AuthenticateResult result)
{
AuthenticateResult = result;
}

public AuthenticateResult? AuthenticateResult
{
get => _result;
set
{
_result = value;
_user = _result?.Principal;
}
}

public ClaimsPrincipal? User
{
get => _user;
set
{
_user = value;
_result = null;
}
}
}
1 change: 1 addition & 0 deletions backend/Origam.Server/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ var authenticationPostProcessor
app.UseIdentityServer();
app.UseMiddleware<FatalErrorMiddleware>();
app.UseUserApi(startUpConfiguration);
app.UseWorkQueueApi();
app.UseAuthentication();
app.UseHttpsRedirection();
if (startUpConfiguration.EnableSoapInterface)
Expand Down
2 changes: 1 addition & 1 deletion backend/Origam.Services/IWorkQueueService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public interface IWorkQueueService : IWorkbenchService
void HandleAction(Guid queueId, string queueClass, DataTable selectedRows,
Guid commandType, string command, string param1, string param2,
object errorQueueId);
void HandleAction(Guid queueEntryId, Guid commandId, bool calledFromApi, string transactionId);
void HandleAction(string workQueueCode, string commandText, Guid queueEntryId);
IDataDocument GenerateNotificationMessage(
Guid notificationTemplateId
, IXmlContainer notificationSource
Expand Down
15 changes: 7 additions & 8 deletions backend/Origam.Workflow/WorkQueue/WorkQueueService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -901,10 +901,9 @@ private static void CheckSelectedRowsCountPositive(int count)
}
}

public void HandleAction(Guid queueEntryId, Guid commandId, bool calledFromApi, string transactionId)
public void HandleAction(string workQueueCode, string commandText, Guid queueEntryId)
{
// get info about queue (from command)
Guid queueId = GetQueueId(commandId);
Guid queueId = GetQueueId(workQueueCode);
// get all queue data from database (no entries)
WorkQueueData queue = GetQueue(queueId);
// extract WorkQueueClass name and construct WorkQueueClass from name
Expand All @@ -913,7 +912,7 @@ public void HandleAction(Guid queueEntryId, Guid commandId, bool calledFromApi,

// authorize access from API
IOrigamAuthorizationProvider auth = SecurityManager.GetAuthorizationProvider();
if (calledFromApi && (queueRow.IsApiAccessRolesNull() || !auth.Authorize(SecurityManager.CurrentPrincipal, queueRow.ApiAccessRoles)))
if (queueRow.IsApiAccessRolesNull() || !auth.Authorize(SecurityManager.CurrentPrincipal, queueRow.ApiAccessRoles))
{
throw new RuleException(
String.Format(ResourceUtils.GetString("ErrorWorkQueueApiNotAuthorized"),
Expand All @@ -925,7 +924,7 @@ public void HandleAction(Guid queueEntryId, Guid commandId, bool calledFromApi,
WorkQueueData.WorkQueueCommandRow commandRow = null;
foreach (WorkQueueData.WorkQueueCommandRow cmd in queue.WorkQueueCommand.Rows)
{
if (cmd.Id == commandId)
if (cmd.Text == commandText)
{
commandRow = cmd;
}
Expand All @@ -935,11 +934,11 @@ public void HandleAction(Guid queueEntryId, Guid commandId, bool calledFromApi,
{
throw new RuleException(
String.Format(ResourceUtils.GetString("ErrorWorkQueueCommandNotAuthorized"),
commandId, queueId),
commandText, queueId),
RuleExceptionSeverity.High, "commandId", "");
}
// fetch a single queue entry
DataSet queueEntryDS = FetchSingleQueueEntry(wqc, queueEntryId, transactionId);
DataSet queueEntryDS = FetchSingleQueueEntry(wqc, queueEntryId, null);

// call handle action
HandleAction(queueId, queueRow.WorkQueueClass, queueEntryDS.Tables[0],
Expand All @@ -949,7 +948,7 @@ public void HandleAction(Guid queueEntryId, Guid commandId, bool calledFromApi,
commandRow.IsParam2Null() ? null : commandRow.Param2,
true,
commandRow.IsrefErrorWorkQueueIdNull() ? (object)null : (object)commandRow.refErrorWorkQueueId,
transactionId);
null);
}

public DataRow GetNextItem(WorkQueueData.WorkQueueRow q, string transactionId,
Expand Down