Skip to content

Add SoapCore.UI: a Swagger-like explorer for SoapCore services - #1199

Open
frankyjquintero wants to merge 2 commits into
DigDes:developfrom
frankyjquintero:feature/soapcore-ui
Open

frankyjquintero wants to merge 2 commits into
DigDes:developfrom
frankyjquintero:feature/soapcore-ui

Conversation

@frankyjquintero

Copy link
Copy Markdown

Add SoapCore.UI: a Swagger-like explorer for SoapCore services

Depends on the ISoapEndpointRegistry PR (#1190). Merge that one first. This branch is built on top of it, and the explorer reads its endpoints from the registry.

Summary

SoapCore.UI is a new, separate NuGet package that adds one page to an ASP.NET Core application: a Swagger-UI-like explorer for the SOAP endpoints SoapCore hosts. It lists the endpoints, shows their operations and documentation, generates a sample SOAP envelope from the endpoint's WSDL and lets you call the endpoint from the browser ("Try it out").

builder.Services.AddSoapCore();
builder.Services.AddSoapCoreUi(options => options.Title = "My services");
// ...
if (app.Environment.IsDevelopment())
{
	app.MapSoapCoreUi();   // /soap-ui
}

It is built on the registry from #1190: nothing is guessed from the route table, and endpoints registered in middleware mode are listed too.

It has no server-side proxy, makes no external request (no CDN, fonts or analytics) and has no npm or build step: the front end is plain JavaScript and CSS embedded in the assembly.

image image image image image image image image

Why a separate package

  • The core SoapCore package keeps its dependencies and target frameworks. SoapCore.UI targets net8.0 only and depends on SoapCore.
  • It adds HTTP endpoints and a page. Nobody gets them unless they install the package and call MapSoapCoreUi().

What it does

Finds and documents the services

  • Lists every endpoint from ISoapEndpointRegistry (routing and middleware mode), grouped by contract, with badges for the SOAP versions and the serializer.
  • Shows the operations with the same names and SOAPAction values SoapCore uses at runtime. With the opt-in ExposeOperationDetails it also shows parameters (in/out/ref), the return type, declared faults and message-contract flags.
  • Descriptions of services, operations and parameters from [Description] and from XML documentation files (IncludeXmlComments).
  • WSDL view: raw and as a structured outline (services, bindings, port types, messages, types), plus a ready-to-copy dotnet-svcutil command.

Lets you try an operation

  • Sample envelope generated from the WSDL, for SOAP 1.1 and 1.2. When the WSDL is not available it is built from the operation details, or from a generic envelope.
  • The request is edited as XML (syntax highlighting, live well-formedness check, Format XML) or in a typed Form view (typed inputs, enum selects, include checkbox for optional elements, add/remove for repeatable ones, nil). The XML is always the source of truth: the form is rebuilt from it whenever you switch to it.
  • Sent from the browser, same origin. Editable SOAPAction and headers, Ctrl+Enter, a timeout field.
  • Response viewer with status, time, size, headers, search and collapsible nodes. SOAP faults get their own panel.
  • Authorize panel: Bearer, Basic, API key and WS-Security UsernameToken (text or digest, the token that AddSoapWsSecurityFilter validates). Credentials stay in memory.
  • Copy the request as curl, PowerShell, C# HttpClient or raw HTTP. Secrets are shown as REDACTED.
  • Session history (last 20), deep links to one operation, Reload, light/dark/system theme, collapsible sidebar on small screens.

How to try it (demo credentials)

dotnet run --project samples/ServerWithUi

Open http://localhost:5290/soap-ui. The http launch profile sets ASPNETCORE_ENVIRONMENT=Development, which is what turns the explorer on in the sample.

These are public demo values for the sample only. They are in the sample's source and README on purpose. Never use them, or the sample's check, to protect a real service.

/Secure.svc answers only callers that authenticate (anything else gets HTTP 401 and a SOAP fault) and accepts any one of these four schemes. Press Authorize in the explorer, pick a scheme, type the value and send WhoAmI, which tells you which scheme was used:

Scheme What is sent User / name Secret
Bearer Authorization: Bearer <token> n/a demo-token
Basic Authorization: Basic <base64 of user:password> demo demo-password
API key the header X-Api-Key (the name is SoapUiOptions.ApiKeyHeaderName) X-Api-Key demo-key
WS-Security UsernameToken a wsse:Security SOAP header, with the password as text or as a digest demo demo-password

Other things to try:

Where What Value
/NoWsdl.svc → EchoWithToken Needs a custom header. Without it the call faults. Add it under "Custom request headers" X-Demo-Token: demo
/Weather.svc → GetReport, GetForecast, GetDailyForecast, GetCurrentCondition, CityExists Known city codes. The generated sample uses string, which is not a city, so GetReport and GetDailyForecast answer with a SOAP fault. Replace it BOG, MDE, NYC or LON
/Weather.svc → GetReport or GetDailyForecast An unknown city raises a WeatherFault, which the explorer shows as a SOAP fault. (GetForecast returns an empty list and GetCurrentCondition answers Cloudy for an unknown city.) any other code, for example ZZZ
/Service.asmx → PingComplexModel A good operation to try the Form view: optional elements, a list, a DateTimeOffset and an enum the generated sample works as it is

Command-line check of /Secure.svc (bash):

URL=http://localhost:5290/Secure.svc
ACTION='"http://soapcore.dev/samples/secure/ISecureService/WhoAmI"'
ENVELOPE='<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sec="http://soapcore.dev/samples/secure"><soapenv:Header/><soapenv:Body><sec:WhoAmI/></soapenv:Body></soapenv:Envelope>'

curl -s "$URL" -H 'Content-Type: text/xml; charset=utf-8' -H "SOAPAction: $ACTION" -H 'Authorization: Bearer demo-token' -d "$ENVELOPE"   # Bearer
curl -s "$URL" -H 'Content-Type: text/xml; charset=utf-8' -H "SOAPAction: $ACTION" -u demo:demo-password -d "$ENVELOPE"                    # Basic
curl -s "$URL" -H 'Content-Type: text/xml; charset=utf-8' -H "SOAPAction: $ACTION" -H 'X-Api-Key: demo-key' -d "$ENVELOPE"                  # API key
curl -si "$URL" -H 'Content-Type: text/xml; charset=utf-8' -H "SOAPAction: $ACTION" -d "$ENVELOPE"                                          # no credentials: HTTP 401

The sample README has the WS-Security envelopes (plain text and a worked digest). The explorer builds them for you when you choose that scheme.

Security model

This is a development and diagnostics tool. The package README has the full model; the main points:

  • No server-side proxy. The browser calls the SOAP endpoints itself and loads the WSDL itself, so the server never makes an outgoing request on behalf of the page and there is no SSRF surface. (The prototype this is based on had a proxy built from the Host header; it was removed.)
  • Protect it with standard conventions. MapSoapCoreUi() returns an IEndpointConventionBuilder (a route group): chain .RequireAuthorization(), .RequireHost() or .RequireCors(). The UI logs a warning when it is served without authorization outside Development.
  • Strict response headers on every UI response: Content-Security-Policy (default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; ...), X-Content-Type-Options, Referrer-Policy, X-Frame-Options, Cache-Control.
  • Same origin only. A GET or HEAD with a foreign Origin is answered with 403 even if the host has a permissive CORS policy. The README explains why this does not stop DNS rebinding (AllowedHosts, RequireHost).
  • The front end uses no innerHTML family, eval, inline script/style or external URLs. Everything coming from a WSDL or a response is written with textContent, and everything inserted into generated XML is escaped. C# tests scan the embedded files for these constructs.
  • Assets come from a fixed whitelist of embedded resources, never from a path built from the request.
  • Credentials are only in memory, never stored, logged or put in the URL. They are redacted in copied code by a header-name heuristic (documented; the rest of a request body is not redacted).
  • Opt-in exposure: ExposeOperationDetails (CLR type names) is off by default. SendTimeoutSeconds and MaxResponseBytes are limits of the page, not protections of the server, and the README says so.

Changes outside the new projects

File Change
src/SoapCore.sln, samples/Sample.sln Add the new projects
src/SoapCore.Tests/SoapCore.Tests.csproj ProjectReference to SoapCore.UI, and the Jint package (see "Tests")
.github/workflows/publish.yml A separate step that packs and pushes SoapCore.UI after SoapCore. A UI failure cannot block the core release, and --skip-duplicate applies to the UI package only
README.md A short "Exploring SOAP services with SoapCore.UI" section that points to the package README

src/SoapCore/** is not touched by this PR.

What is new

Path Content
src/SoapCore.UI The package: options and validation, AddSoapCoreUi, MapSoapCoreUi, the catalog built from the registry (cached ServiceDescription, descriptions provider), the asset whitelist, and EmbeddedUI/ (HTML, 4 JS files, 2 CSS files)
samples/ServerWithUi A runnable sample with six endpoints: DataContract and XmlSerializer, SOAP 1.1 and 1.2, a middleware-mode endpoint, faults, a one-way operation, XML descriptions, and /Secure.svc, which accepts a bearer token, Basic, an API key or a WS-Security UsernameToken to try the Authorize panel (public demo credentials)
src/SoapCore.Tests/Ui The tests

Rough size: about 3,000 lines of C#, 10,000 of JavaScript and 2,900 of CSS/HTML in the package; the tests are larger than the code.

Tests

dotnet test src/SoapCore.Tests passes (2,666 tests in the project, about 2,350 of them new).

  • Server side (TestServer): catalog JSON in both hosting modes, ids for duplicate paths, PathBase and EndpointPathPrefix, option validation, descriptions and XML comments (including malformed files and DTDs), operation details and their opt-in, asset serving (headers, ETag/304, HEAD, 405, path traversal), the cross-origin 403, authorization chaining, and the absence of any proxy route.
  • Front-end scans: the embedded JS/HTML/CSS must not contain the banned constructs, absolute URLs must be on an allow-list, every id the script uses must exist in the page.
  • JavaScript logic, in the CI. The pure functions of wsdl-parser.js (escaping, validators, XSD value validation with real ranges, code-snippet builders with hostile inputs, WS-Security digest, XML tokenizer, search, form template, WSDL outline) are executed inside the Jint interpreter, so the existing dotnet test job runs them without Node. The two functions that need a DOM run against a small XML parser shim included in the test file, which was checked against real Chromium on 11 WSDLs.

Verification beyond the automated tests

  • The sample was exercised in a real browser: all 48 operation/SOAP-version combinations of the sample generate an envelope and a form template and mount the form without errors; a form edit round-trips to well-formed, escaped XML that the server accepts; Bearer, Basic, API key and WS-Security (text and digest) authenticate against /Secure.svc; code snippets leak none of the typed secrets in any of the four formats.
  • dotnet-svcutil generates a client from the WSDL of every sample endpoint.
  • SoapCore.UI builds with no warnings (StyleCop and analyzers are on and warnings are errors in the project).

Things you should know before merging

  1. Release gate. SoapCore.UI needs the registry, which is not in the published SoapCore 1.2.1.16. dotnet pack src/SoapCore.UI fails on purpose (a guard in the csproj) until src/SoapCore/SoapCore.csproj has a <Version> of 1.2.1.17 or later, so a package with an unsatisfiable dependency cannot be pushed by mistake. AddSoapCoreUi also throws a clear error at run time against an older SoapCore. Bumping the version is left to the maintainers.
  2. NuGet. The API key used by publish.yml must be allowed to push the new SoapCore.UI package id.
  3. CodeQL. The repository now contains JavaScript, so javascript should be added to the languages of codeql-analysis.yml. I did not change that workflow.
  4. Target framework. The package is net8.0 only, which is the newest SDK the CI installs. Nothing in it needs a newer one.
  5. Size. This is a large PR. If it is easier to review in parts, the natural cut lines are: (a) the package without the form view (form-editor.js and its CSS are self-contained and can be dropped behind the existing EnableFormEditor option), (b) the sample, (c) the front-end tests. I can split it on request.

Limitations (documented in the package README)

  • app.Map(...) and UsePathBase(...) prefixes are not in the registry. EndpointPathPrefix covers them with one prefix for all endpoints.
  • HttpGetEnabled is needed to show the WSDL. Without it the explorer builds the sample from the operation details (with ExposeOperationDetails) or from a generic envelope.
  • Same origin only. MessageVersion.None endpoints are listed but cannot be called. File-based WSDL imports are not followed.
  • The form view does not model xs:choice exclusivity or xs:any (shown read-only, edited in the XML view) and cannot represent mixed content. Requests over 256 KB are shown without syntax highlighting.
  • Not tested: Firefox and Safari, screen readers, a real clipboard.

SoapCore issues found while building this (not changed here)

These live in the library, outside the scope of this PR, and probably deserve their own issues:

  • Duplicate binding and port names. With DataContractSerializer, an endpoint with both a SOAP 1.1 and a SOAP 1.2 encoder and no BindingName/PortName gets the same name for both bindings and both ports in its WSDL (MetaWCFBodyWriter.GetSoapMetaParameters; the XmlSerializer writer adds a _soap/_soap12 suffix). dotnet-svcutil then fails with More than one binding named .... The sample works around it by naming the SOAP 1.2 encoder.
  • DateTimeOffset and Guid as xsd:string. With XmlSerializer the WSDL says nothing about the real type. The explorer guesses a DateTimeOffset by element name.
  • urn: DataContract namespaces. The schema namespace is prefixed with http://schemas.datacontract.org/2004/07/ but the type references are not, so the WSDL is inconsistent.
  • HTTP operation routes bypass message processors. ProcessHttpOperation (GET/POST {path}/{Operation}, enabled by HttpGetEnabled/HttpPostEnabled) does not go through ISoapMessageProcessor, IAsyncMessageFilter or IMessageInspector2. Anyone who protects an endpoint with one of those can be bypassed through that route. I found this by reading the middleware and by the sample's /Secure.svc, which needs a small guard for it; I did not try to exploit it beyond that.

Questions for the maintainers

  1. Do you want the explorer in this repository as a second package, or would you rather it lives in its own repository and this PR becomes a link from the README? The code works either way.
  2. Is net8.0 only acceptable, and is a 0.1.0-preview.1 version with its own release step the right way to ship it?
  3. Do you want the front-end tests (Jint) in the main test project as they are, or in a separate project?

Checklist

  • Opt-in: nothing changes for users of SoapCore alone, and src/SoapCore is untouched
  • Covered by tests (server side, front-end scans and JavaScript logic), all green in the existing CI job
  • Documented: package README, short section in the main README, sample README
  • No server-side proxy, no external requests, strict CSP
  • Release gate: SoapCore version bump (maintainers)
  • publish.yml and CodeQL changes reviewed (maintainers)

Permite el listado programático de los servicios SOAP configurados con UseSoapEndpoint.
Resuelve la limitación de que EndpointDataSource de ASP.NET Core no expone detalles suficientes para los servicios SoapCore. Esto facilita la creación de catálogos de servicios, gateways y herramientas de observabilidad.
La funcionalidad es opt-in, thread-safe y registra un snapshot de la configuración del endpoint.
Este nuevo paquete proporciona una interfaz gráfica similar a Swagger que permite descubrir, documentar y probar endpoints SOAP configurados con SoapCore. Utiliza `ISoapEndpointRegistry` para listar los servicios, generar envolventes SOAP de ejemplo a partir del WSDL y enviar solicitudes directamente desde el navegador. Mejora la experiencia del desarrollador y facilita la depuración de servicios. Incluye un nuevo proyecto de ejemplo (`ServerWithUi`) y pruebas exhaustivas.
Requiere SoapCore 1.2.1.17 o posterior para funcionar.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant