From 6c6ac272cd4385aebce507bc74d7d493a11fea41 Mon Sep 17 00:00:00 2001 From: Meik Date: Fri, 26 Jun 2026 11:44:34 +0200 Subject: [PATCH] feat: support Matrix42 26.1 sandboxed web api --- .gitignore | 94 ++--------- F4SDHelper/F4SD - Helper.csproj | 73 ++------ F4SDM42WebApi/C4ITF4SDM42WebApi.dll.host | 5 + F4SDM42WebApi/F4SD - M42WebApi.csproj | 178 ++++++++------------ F4SDM42WebApi/F4SDHelperService.cs | 47 +++++- F4SDM42WebApi/F4SDM42WebApiController.cs | 96 ++++++----- F4SDM42WebApi/M42SandboxedExtension.targets | 63 +++++++ F4SDM42WebApi/package.json | 16 ++ 8 files changed, 267 insertions(+), 305 deletions(-) create mode 100644 F4SDM42WebApi/C4ITF4SDM42WebApi.dll.host create mode 100644 F4SDM42WebApi/M42SandboxedExtension.targets create mode 100644 F4SDM42WebApi/package.json diff --git a/.gitignore b/.gitignore index 688980b..49647ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,81 +1,13 @@ -# -------------------------------------------------------------------------------------------------- -# Core Visual Studio / .NET build outputs -# -------------------------------------------------------------------------------------------------- -bin/ -obj/ -[Bb]uild/ -[Dd]ebug*/ -[Rr]elease*/ -x64/ -x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -_ReSharper.*/ -*.user -*.userosscache -*.suo -*.cache -*.pdb -*.mpdb -*.opendb -*.VC.db -*.vscode/ - -# -------------------------------------------------------------------------------------------------- -# Package artefacts (NuGet, npm, etc.) -# -------------------------------------------------------------------------------------------------- -/packages/ -*.nupkg -*.snupkg -*.nuspec -package-lock.json -packages.config -npm-debug.log* -yarn-error.log - -# -------------------------------------------------------------------------------------------------- -# TFS / TFVC specific files and folders -# -------------------------------------------------------------------------------------------------- -$tf/ -*$tf/ -*.tf -*.tfs -.tfignore - -# -------------------------------------------------------------------------------------------------- -# IDE tooling and workspace settings -# -------------------------------------------------------------------------------------------------- -.vs/ -.vscode/ -*.swp -*.sln.docstates -*.VC.VC.opendb -.idea/ -.DS_Store -Thumbs.db - -# -------------------------------------------------------------------------------------------------- -# Test results and profiling data -# -------------------------------------------------------------------------------------------------- -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* -*.trx -*.coverage -*.coveragexml -*.opencover.xml -*.dotCover -BenchmarkDotNet.Artifacts/ - -# -------------------------------------------------------------------------------------------------- -# Miscellaneous recommended ignores -# -------------------------------------------------------------------------------------------------- -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -*.tmp -*.temp -*.log -*.bak -*.orig -*.scc - +bin/ +obj/ +.vs/ +*.user +packages/ +F4SDHelper/packages.config +F4SDM42WebApi/packages.config +F4SDM42WebApi/BasePackage/Assemblies/ +F4SDM42WebApi/Root/InstalledPackages/ +F4SDHelper/bin/ +F4SDHelper/obj/ +F4SDM42WebApi/bin/ +F4SDM42WebApi/obj/ diff --git a/F4SDHelper/F4SD - Helper.csproj b/F4SDHelper/F4SD - Helper.csproj index 7dc3d72..1939174 100644 --- a/F4SDHelper/F4SD - Helper.csproj +++ b/F4SDHelper/F4SD - Helper.csproj @@ -1,63 +1,25 @@ - - - + - Debug - AnyCPU - {EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE} + net8.0 Library - Properties C4IT.F4SDM C4ITF4SDM42WebApiHelper - v4.7.2 - 512 - true - - SAK - SAK - SAK - SAK - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - bin\Release_and_copy\ - TRACE - true - pdbonly - AnyCPU - 7.3 - prompt + disable + disable + latestmajor + false + false + - - ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + + ..\F4SDM42WebApi\M42Libraries\26.1\Newtonsoft.Json.dll + false - - - - - - - - + + Common\C4IT.Logging.LogManager.cs @@ -67,11 +29,6 @@ Properties\SharedAssemblyInfo.cs - - - - - - - \ No newline at end of file + + diff --git a/F4SDM42WebApi/C4ITF4SDM42WebApi.dll.host b/F4SDM42WebApi/C4ITF4SDM42WebApi.dll.host new file mode 100644 index 0000000..22e782f --- /dev/null +++ b/F4SDM42WebApi/C4ITF4SDM42WebApi.dll.host @@ -0,0 +1,5 @@ + + + + + diff --git a/F4SDM42WebApi/F4SD - M42WebApi.csproj b/F4SDM42WebApi/F4SD - M42WebApi.csproj index be3676e..82666de 100644 --- a/F4SDM42WebApi/F4SD - M42WebApi.csproj +++ b/F4SDM42WebApi/F4SD - M42WebApi.csproj @@ -1,139 +1,93 @@ - - - + - Debug - AnyCPU - {D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE} + net8.0 Library - Properties C4IT.F4SDM C4ITF4SDM42WebApi - v4.7.2 - 512 - true - - SAK - SAK - SAK - SAK - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Debug_and_copy\ - DEBUG;TRACE - full - AnyCPU - 7.3 - prompt - - - bin\Release_and_copy\ - TRACE - true - pdbonly - AnyCPU - 7.3 - prompt + win-x64;linux-x64 + true + disable + disable + latestmajor + false + false + d8cbffca-0b43-4acc-80ea-c944e7420cee + C4ITF4SD*.* + - - False - M42Libraries\Matrix42.Common.dll + + M42Libraries\26.1\Matrix42.Common.dll + false - - False - M42Libraries\Matrix42.Contracts.Common.dll + + M42Libraries\26.1\Matrix42.Contracts.Common.dll + false - - False - M42Libraries\Matrix42.Contracts.Platform.dll + + M42Libraries\26.1\Matrix42.Contracts.Platform.dll + false - - False - M42Libraries\Matrix42.Contracts.ServiceManagement.dll + + M42Libraries\26.1\Matrix42.Contracts.ServiceManagement.dll + false - - False - M42Libraries\Matrix42.Pandora.Contracts.dll + + M42Libraries\26.1\Matrix42.Hosting.Contracts.dll + false - - False - M42Libraries\Matrix42.Services.Description.Contracts.dll + + M42Libraries\26.1\Matrix42.Configuration.Global.dll + false - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + M42Libraries\26.1\Matrix42.Pandora.Contracts.dll + false - - - - - - False - M42Libraries\System.Web.Http.dll + + M42Libraries\26.1\Matrix42.Services.Description.Contracts.dll + false - - False - M42Libraries\System.Web.OData.dll + + M42Libraries\26.1\Matrix42.WebApi.Contracts.dll + false - - - - - - - - False - M42Libraries\update4u.SPS.DataLayer.dll + + M42Libraries\26.1\Newtonsoft.Json.dll + false - - False - M42Libraries\update4u.SPS.Utility.dll + + M42Libraries\26.1\System.Net.Http.Formatting.dll + false + + + M42Libraries\26.1\update4u.SPS.DataLayer.dll + false + + + M42Libraries\26.1\update4u.SPS.Utility.dll + false + + + + + - - Properties\SharedAssemblyInfo.cs - + + Properties\SharedAssemblyInfo.cs + + - - {eb95e1fb-7a9e-4894-bd28-2d0be2715ebe} - F4SD - Helper - True - - - - - - + + - - - @echo powershell -ExecutionPolicy Unrestricted $(ProjectDir)deploy.ps1 -ProjectDir $(ProjectDir) -SolutionDir $(SolutionDir) -OutDir $(OutDir) -ConfigurationName $(ConfigurationName) - - \ No newline at end of file + + diff --git a/F4SDM42WebApi/F4SDHelperService.cs b/F4SDM42WebApi/F4SDHelperService.cs index 4fa9d8a..49f7cc8 100644 --- a/F4SDM42WebApi/F4SDHelperService.cs +++ b/F4SDM42WebApi/F4SDHelperService.cs @@ -13,9 +13,9 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -using System.Web; using Matrix42.Common; +using Matrix42.WebApi.Contracts; using update4u.SPS.DataLayer; using update4u.SPS.DataLayer.Transaction; @@ -80,7 +80,7 @@ namespace C4IT.F4SD { LogEntry($"Generating F4SD URI for type: '{type}', GUID: '{EOID}'", LogLevels.Debug); var builder = new UriBuilder(c4itf4sdmonLinkBase); - var queryString = HttpUtility.ParseQueryString(builder.Query); + var queryString = UrlQueryBuilder.Parse(builder.Query); switch (type.ToLowerInvariant().Split('.').Last()) { @@ -1663,7 +1663,7 @@ namespace C4IT.F4SD presetParamsDyn.SPSActivityClassIncident.Asset = asset.Id; } var type = TicketAndServiceRequestEnabled ? "SPSActivityTypeTicket" : "SPSActivityTypeIncident"; - var presetParams = HttpUtility.UrlEncode(JsonConvert.SerializeObject(presetParamsDyn)); + var presetParams = WebUtility.UrlEncode(JsonConvert.SerializeObject(presetParamsDyn)); var directLink = string.Format(TicketDirectLinkCreateTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, type, presetParams); return new DirectLink() { @@ -1848,7 +1848,7 @@ namespace C4IT.F4SD viewOptionsDyn.type = CIName; viewOptionsDyn.viewType = "action"; viewOptionsDyn.actionId = TicketCloseActionId; - var viewOptions = HttpUtility.UrlEncode(JsonConvert.SerializeObject(viewOptionsDyn)); + var viewOptions = WebUtility.UrlEncode(JsonConvert.SerializeObject(viewOptionsDyn)); var ticketDirectLinkPreview = string.Format(TicketDirectLinkPreviewTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, CIName, ActivityEOID); var ticketDirectLinkEdit = string.Format(TicketDirectLinkEditTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, CIName, ActivityEOID); @@ -2305,7 +2305,7 @@ namespace C4IT.F4SD } zipArchive.Dispose(); memoryStream.Position = 0L; - HttpResponseMessage httpResponseMessage = request.CreateResponse(HttpStatusCode.OK); + HttpResponseMessage httpResponseMessage = HttpResponseExtensions.CreateResponse(request, HttpStatusCode.OK); httpResponseMessage.Content = new StreamContent(memoryStream); httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { @@ -2727,4 +2727,41 @@ namespace C4IT.F4SD Roles = new List(); } } + + internal sealed class UrlQueryBuilder + { + private readonly List> _values = new List>(); + + private UrlQueryBuilder() + { + } + + public static UrlQueryBuilder Parse(string query) + { + var builder = new UrlQueryBuilder(); + var normalizedQuery = (query ?? string.Empty).TrimStart('?'); + + foreach (var pair in normalizedQuery.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)) + { + var separatorIndex = pair.IndexOf('='); + var key = separatorIndex >= 0 ? pair.Substring(0, separatorIndex) : pair; + var value = separatorIndex >= 0 ? pair.Substring(separatorIndex + 1) : string.Empty; + + builder.Add(WebUtility.UrlDecode(key), WebUtility.UrlDecode(value)); + } + + return builder; + } + + public void Add(string key, string value) + { + if (!string.IsNullOrEmpty(key)) + _values.Add(new KeyValuePair(key, value ?? string.Empty)); + } + + public override string ToString() + { + return string.Join("&", _values.Select(pair => $"{WebUtility.UrlEncode(pair.Key)}={WebUtility.UrlEncode(pair.Value)}")); + } + } } diff --git a/F4SDM42WebApi/F4SDM42WebApiController.cs b/F4SDM42WebApi/F4SDM42WebApiController.cs index f86aab3..f6a3770 100644 --- a/F4SDM42WebApi/F4SDM42WebApiController.cs +++ b/F4SDM42WebApi/F4SDM42WebApiController.cs @@ -6,16 +6,14 @@ using System.Net; using System.Net.Http; using System.Reflection; using System.Threading.Tasks; -using System.Web; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.OData; -using System.Web.OData.Query; using Matrix42.Common; using Matrix42.Contracts.ServiceManagement.ServiceContracts; +using Matrix42.Hosting.Contracts; using Matrix42.Pandora.Contracts; using Matrix42.Services.Description.Contracts; +using Matrix42.WebApi.Contracts; +using Matrix42.WebApi.Contracts.OData; using update4u.SPS.Utility.GlobalConfiguration; using C4IT.F4SDM; @@ -42,36 +40,25 @@ namespace C4IT.F4SD private readonly F4SDHelperService _f4stHelperService; - public string BaseUrl => $"{Request.RequestUri.Scheme}://{Request.RequestUri.Host}"; + public string BaseUrl => Request?.RequestUri == null ? string.Empty : $"{Request.RequestUri.Scheme}://{Request.RequestUri.Host}"; public string EndpointBaseUrl => $"{BaseUrl}/m42Services/api/c4itf4sdwebapi"; - public F4SDM42WebApiController( - //IObjectService objectService, - //IIncidentService incidentService, - IJournalService journalService - //IFragmentService fragmentService, - , IEntityDataService entityDataService - , IPandoraUserProfile userProfile - ) + + public F4SDM42WebApiController(IDependencyResolver resolver) { defaultInstance = this; - //_objectService = objectService; - //_fragmentService = fragmentService; - //_incidentService = Guard.NullArgument(incidentService, "incidentService"); - - _entityDataService = entityDataService; - _journalService = journalService; + _entityDataService = resolver?.TryGet(); + _journalService = resolver?.TryGet(); _globalConfigurationProvider = GlobalConfigurationProvider.Instance; _f4stHelperService = new F4SDHelperService(); - _userProfile = userProfile; + _userProfile = resolver?.TryGet(); + EnsureInitialized(); } - private static object initLock = new object(); - protected override void Initialize(HttpControllerContext controllerContext) + private static readonly object initLock = new object(); + private static void EnsureInitialized() { - base.Initialize(controllerContext); try { - //System.Diagnostics.Debugger.Launch(); lock (initLock) { if (IsInitialized || F4SDM42LogsWebApiController.IsInitialized) @@ -311,18 +298,18 @@ namespace C4IT.F4SD Values = vals.ToArray() }; //CacheOutputAttribute.RegisterResponseEtag($"enum_{enumeration.Name}_{(int)mode}", $"{enumeration.Name}_{(int)mode}", cultureInvariant: false, userInvariant: true, val); - return Request.CreateResponse(HttpStatusCode.OK, enumeration); + return HttpResponseExtensions.CreateResponse(Request, HttpStatusCode.OK, enumeration); } - [Route("getMyRoleMemberships", Order = 2), HttpGet] + [Route("getMyRoleMemberships"), HttpGet] public async Task getMyRoleMemberships() { var User = _userProfile.GetInteractiveUserInfo(); var filter = AsqlHelper.BuildInCondition("ID", new Guid[] { User.Id }); - return Request.CreateResponse(HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); + return HttpResponseExtensions.CreateResponse(Request, HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); } - [Route("getRoleMemberships", Order = 1), HttpGet] + [Route("getRoleMemberships"), HttpGet] public async Task getRoleMemberships([FromUri] GetRoleMembershipsRequest req) { var filter = ""; @@ -342,7 +329,7 @@ namespace C4IT.F4SD if (!string.IsNullOrEmpty(filter)) { - return Request.CreateResponse(HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); + return HttpResponseExtensions.CreateResponse(Request, HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); } else { @@ -350,11 +337,11 @@ namespace C4IT.F4SD } } - [Route("getRoleMemberships/{userId}", Order = 1), HttpGet] + [Route("getRoleMemberships/{userId}"), HttpGet] public async Task getRoleMembershipForUser(Guid userId) { var filter = AsqlHelper.BuildInCondition("ID", new Guid[] { userId }); - return Request.CreateResponse(HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); + return HttpResponseExtensions.CreateResponse(Request, HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); } [Obsolete] @@ -391,7 +378,7 @@ namespace C4IT.F4SD .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) .Select(part => { - var decodedPart = HttpUtility.UrlDecode(part)?.Trim(); + var decodedPart = WebUtility.UrlDecode(part)?.Trim(); if (string.IsNullOrWhiteSpace(decodedPart)) return null; @@ -477,10 +464,9 @@ namespace C4IT.F4SD public static bool IsInitialized { get; private set; } = false; - private static object initLock = new object(); - protected override void Initialize(HttpControllerContext controllerContext) + private static readonly object initLock = new object(); + private static void EnsureInitialized() { - base.Initialize(controllerContext); try { lock (initLock) @@ -502,20 +488,15 @@ namespace C4IT.F4SD public F4SDM42LogsWebApiController() { _f4stHelperService = new F4SDHelperService(); + EnsureInitialized(); } [Route(""), HttpGet] - [EnableQuery] public IEnumerable getLog2(ODataQueryOptions queryOptions) { try { - IQueryable queryable = _f4stHelperService.privGetLog2().AsQueryable(); - if (queryOptions.Filter != null) - { - queryable = queryOptions.Filter.ApplyTo(queryable, new ODataQuerySettings()).Cast(); - } - return queryable; + return ApplyLogFilter(_f4stHelperService.privGetLog2(), queryOptions); } catch (Exception E) { @@ -528,12 +509,7 @@ namespace C4IT.F4SD [HttpGet] public int Log2Count(ODataQueryOptions queryOptions) { - IQueryable queryable = _f4stHelperService.privGetLog2().AsQueryable(); - if (queryOptions.Filter != null) - { - queryable = queryOptions.Filter.ApplyTo(queryable, new ODataQuerySettings()).Cast(); - } - return queryable.Count(); + return ApplyLogFilter(_f4stHelperService.privGetLog2(), queryOptions).Count(); } [Route("{id}")] @@ -542,6 +518,28 @@ namespace C4IT.F4SD { return _f4stHelperService.privGetLog2(id); } + + private static IEnumerable ApplyLogFilter(IEnumerable entries, ODataQueryOptions queryOptions) + { + var result = entries ?? Enumerable.Empty(); + var filter = queryOptions?.Filter; + + if (!string.IsNullOrWhiteSpace(filter)) + { + result = result.Where(entry => + (entry.Message?.IndexOf(filter, StringComparison.OrdinalIgnoreCase) ?? -1) >= 0 || + (entry.logLvl?.IndexOf(filter, StringComparison.OrdinalIgnoreCase) ?? -1) >= 0 || + (entry.Theme?.IndexOf(filter, StringComparison.OrdinalIgnoreCase) ?? -1) >= 0); + } + + if (queryOptions?.Skip != null) + result = result.Skip(queryOptions.Skip.Value); + + if (queryOptions?.Top != null) + result = result.Take(queryOptions.Top.Value); + + return result; + } } public class cGetPropertyBody diff --git a/F4SDM42WebApi/M42SandboxedExtension.targets b/F4SDM42WebApi/M42SandboxedExtension.targets new file mode 100644 index 0000000..56bd181 --- /dev/null +++ b/F4SDM42WebApi/M42SandboxedExtension.targets @@ -0,0 +1,63 @@ + + + $(MSBuildProjectDirectory)\BasePackage\Assemblies\ + $(MSBuildProjectDirectory)\Root\InstalledPackages\Assemblies\$(M42ExtensionId)\ + $(MSBuildProjectName)*.* + win-x64;linux-x64 + + + + + <_M42RidList Include="$(M42DefaultRids)" /> + + + + + + true + + + + + + <_M42FlatRoot Include="$(TargetDir)$(M42AssemblyPattern)" /> + + + + + + + + + + + + <_M42Rid Include="$(TargetDir)**\$(M42AssemblyPattern)" /> + + + + + + + + + diff --git a/F4SDM42WebApi/package.json b/F4SDM42WebApi/package.json new file mode 100644 index 0000000..23694de --- /dev/null +++ b/F4SDM42WebApi/package.json @@ -0,0 +1,16 @@ +{ + "Id": "d8cbffca-0b43-4acc-80ea-c944e7420cee", + "Vendor": "Consulting4IT GmbH", + "Name": "C4IT.F4SD.M42WebApi", + "Version": "1.3.0.0", + "Prerequisites": { + "MinimalRequiredProductVersion": "26.1.0", + "DependentPackages": [] + }, + "SetupDirectives": { + "MaintenanceMode": false, + "RecycleWebApplication": false, + "RestartM42WindowsServices": false + }, + "Sandboxed": true +}