# Technical documentation — legitimate_licences

Evidence date: 2026-10-10.

Source: `git@bitbucket.org:overdrive/legitimate_licences.git`, `main` at `707aa72` (2024-11-24, "first commit"). One commit. No README in the tree.

Connection strings, the API key, the Sentry DSN, and the PDF licence key are committed in config files. Values are intentionally omitted here.

## Architecture

The system is a set of .NET Framework class libraries composed into four runnable hosts:

1. **Web** — ASP.NET MVC 5 application `Neo.LegitimateLicences.Web`. Modules are referenced projects. Views for those modules are copied into the web `Views` tree (the same view names also exist inside some module projects).
2. **API** — ASP.NET Web API project `Neo.LegitimateLicences.Api`, attribute routes under `~/1.0/`.
3. **Windows service** — `Neo.LegitimateLicences.WinService`, which starts `Neo.Afx.Services.DatabaseServiceScheduler`.
4. **Desktop** — WinForms `Neo.LegitimateLicences.Desktop`, started by a registered URL protocol, plus .NET 4.0 print controls in `Neo.LegitimateLicences.ActiveX.Printing`.

Shared behaviour (security, workflow, documents, comments, correspondence, audit, queries) lives in `Neo.Afx.Core`, `Neo.Afx.Services.Data`, `Neo.Afx.Services.Admin.Data`, and `Neo.Afx.Services.Web`. `Deploy/NeoAfxSharedAssemblies/AfxServices` contains `.svc` and `.ashx` endpoints for those services. `RefreshAssembliesFromNeoAfxMaster.bat` copies binaries from `C:\Development\NeoAfrica\Neo.Afx` into a local `LegitimateLicences\Assemblies` folder. That path is a developer machine path.

**Inferred:** AFX is an internal Neo Africa framework, vendored into this repository as source plus a deploy folder, not consumed as a NuGet package.

## Solutions and target frameworks

| Solution | Role |
|---|---|
| `Neo.LegitimateLicences.Web.sln` | Web, modules, consoles, workflow projects, data, common, CDN, reports, AFX |
| `Neo.LegitimateLicences.Api.sln` | API |
| `Neo.LegitimateLicences.WinService.sln` | Windows service |
| `Neo.LegitimateLicences.ActiveX.sln` | Printing controls |
| Nested `Desktop`, `Cdn`, and `Verifier` solutions | Those hosts or libraries on their own |

| Target | Projects |
|---|---|
| .NET Framework 4.6.2 | Web, API, most modules, data, AFX core and services |
| .NET Framework 4.5.2 | `Neo.LegitimateLicences.Reports` |
| .NET Framework 4.0 | `ActiveX.Printing`, `Neo.Afx.WinService.Common`, `Messages`, `Workflows` |

`azure-pipelines.yml` builds `**/*.sln` with VSBuild, `DeployOnBuild`, web package as a single zip, platform Any CPU, configuration Release, then runs VSTest. The YAML trigger is branch `master`. The Git branch is `main`. No test projects or test attributes were found, so VSTest has nothing in this tree to execute.

## Web application

- MVC 5.2.7, Razor 3.2.7, OWIN 2.1, SignalR 2.2.1.
- Forms authentication. Cookie role manager name `.NeoLegitimate_ASPROLES`. Application name `Neo Legitimate`.
- Providers: `Neo.Afx.Security.DomainMembershipProvider`, `DomainRoleProvider`, `DomainProfileProvider` in `Neo.Afx.Core` version 1.0.0.0, token `19e71ac1761ebd3c`.
- `Global.asax.cs` initialises Sentry when `SentryDsn` is set, then calls `DatabaseUpdator.UpdateDB()`.
- App settings that matter: `BaseUrl`, `AfxServicesPath`, `AfxCdnPath`, `FingerprintAuthenticationEnabled`, `SentryDsn`, `SystemUsername` (`info@neoafrica.com`), `WebsupergooPdfKey_DisabledOnTest`, `ExternalRouteIgnores`, `aspnet:MaxJsonDeserializerMembers` = 1000000.
- Committed local URLs: base `http://localhost:8000`, CDN `http://localhost:8001`.
- `customErrors` is `Off`. `compilation debug` is `true`.
- DevExpress Web 19.1 handlers for report designer, viewer, and query builder.
- Controllers in the web project itself are thin: `Home`, `Profile`, `Reports`, `Error`, `Auth`, and `Afx` partials for tasks, documents, comments, correspondence, audit, verification, and workflow instances. Feature controllers live in the module projects.

## API

`AuthorizationHeaderHandler` is a `DelegatingHandler`.

- Path contains `/1.0/login`: only `api_Key` is checked against `LegitimateLicencesApiKey`. No action with that route exists on `LegitimateLicencesApiController`.
- Every other path: `api_Key` must match and `api_User` must resolve via `SecurityDatabase.GetUser`. Claims set are name, email, and `UserID`.

Routes found:

| Method | Route | Behaviour |
|---|---|---|
| GET | `~/1.0/ping` | Health-style ping on the main controller |
| GET | `~/1.0/GetData/GetVerifiedLicenceToUseXml` | Query `olNumber`. Executes `Verified.Pr_VerifiedLicenceToUseGet`. Returns XML `response` with attribute `found` |
| GET | `~/1.0/GetData/GetImportedLicenceToUseXml` | Imported licence XML |
| GET | `~/1.0/GetData/LTPSApplicationXmlSearch` | LTPS search |
| GET | `~/1.0/GetData/GetApplications` | Application list |
| GET | `~/1.0/Scanning/GetScanningData` | Data for the desktop scanner |
| POST | `~/1.0/Scanning/UploadScannedDocument` | Upload |
| GET | `~/1.0/Printing/GetPrintoutData` | Payload for the desktop printers |

`DocumentStoreLocationID` is an app setting (committed value `3`). `BlobDownload.ashx` is a separate handler.

Invalid claims throw from `IsValidClaim` using `Error_ClaimDetailsNotFound`. SQL failures on the verified-licence call are returned as an XML error document rather than an HTTP error. Provenance: **Implemented** in `GetVerifiedLicenceToUseXml`.

## Desktop and printing

`Program.Main` requires arguments.

- `registerprotocol` writes the `legitimatedesktop` protocol for `Neo.LegitimateLicences.Desktop.exe`.
- Otherwise the first argument must contain `legitimatedesktop`. The payload is split on `_` into exactly three parts: verb (`print` or `scan`), a user key, and a data token.
- Print calls `LegitimateLicencesApiHelper.GetPrintoutData`. Scan uses the scanning endpoints.
- Printers: `AppReceiptPrinter`, `PaymentAdvicePrinter`, `OperatingLicencePrinter`, `AccreditationCertificatePrinter`, `AccreditationDecalPrinter`, `TemporaryReplacementLicencePrinter`.
- `Neo.LegitimateLicences.DesktopUpdater` is a separate WinExe. How the web decides to launch it was not fully traced.

`Neo.LegitimateLicences.ActiveX.Installer` is a folder. It has no `.csproj` in the project list gathered from the tree. What it installs is **Unknown / Requires Confirmation**.

## Windows service

`ProjectService.OnStart` constructs `DatabaseServiceScheduler` and calls `Start`. `OnStop` stops it. The service name and account are in `ProjectInstaller` and were not opened in full. Install artefacts are under `WIndowServiceInstall` (spelling as in the repository). `install.bat` runs `installutil.exe` from `Framework\v4.0.30319` against `C:\Installs\LegitimateLicense\Neo.LegitimateLicences.WinService.exe`. `uninstall.bat` is beside it. The folder also contains built exe/dll files, install logs, and `Interop.Clickatell.dll`. Config files point at catalog `LegitimateNATIONAL`.

`SystemTriggerHandler.Run`:

1. Dequeue one system trigger using connection setting `ConnectionString`.
2. If a row exists, open a SignalR `HubConnection` to setting `SignalRPath`, hub `MessageHub`.
3. Load verification JSON with `GetVerificationTaskDetail(relatedItemId, 1)`.
4. Invoke `AddVerificationToList`.
5. On invoke failure, append "Error sending data".
6. Always mark the trigger Completed when a row was dequeued.

Which other AFX workers the scheduler loads is data-driven from the AFX services tables. Those worker rows are not scripted as a full list in this repo. **Unknown / Requires Confirmation** beyond the system-trigger class that is compiled in `WinService.Extensions` / `SystemTriggers`.

## Data

SQL Server. Catalog name in every inspected connection string: `LegitimateNATIONAL`. Login name `Legitimate` is committed with a password (not reproduced). Some connection strings use a private host, others `(local)`. Web, API, Windows service, and `WIndowServiceInstall` configs are not identical.

Entity Framework 6.2.0. Main model `Neo.LegitimateLicences.Data/Models/LegitimateDataModel.edmx`. Schemas referenced there: `App`, `Audit`, `Crm`, `Doc`, `Md`, `Purification`, `System`, `Verified`. A second model `Ltps/LtpsDataModel.edmx` uses schema `dbo` for `LtpsData` and `Pr_LtpsDataInsert`.

AFX edmx files cover audit, comments, correspondence, documents, forms, notifications, performance, queries, security, and workflow.

Representative stored procedures (names only): application search and adjudication (`Pr_ApplicationSearch`, `Pr_AdjudicationCreate`, `Pr_ApplicationAdjudicationDecisionCommit`), gazette and dashboard summaries, `Pr_BusinessRuleBypassRequestInprogressGet`, purification duplicate-route procedures, `Verified.Pr_VerifiedLicenceToUseGet`, `App.Pr_Svc_SendUpliftmentNotifications`, `App.Pr_Svc_UpcomingExpiryNotifications`.

### DbUp

`DatabaseUpdator.UpdateDB` calls `EnsureDatabase.For.SqlDatabase` on connection string name `ConnectionString`, then runs embedded scripts whose resource name contains the substring `Legitimate`, and writes a Sentry message for success or failure. Exceptions are captured and swallowed.

Embedded resources in the web csproj:

- `DBUp/Scripts/2023/2205-EmailSenderUpdate-Legitimate.sql` — name contains `Legitimate`, so this script is selected.
- `DBUp/Scripts/2023/2205-Vw_OfficeLocationssql` — filename has no dot before `sql` and does not contain `Legitimate`.
- `DBUp/Scripts/2023/2206-Pr_WorkflowInstanceTaskAction.sql` — does not contain `Legitimate`.

**Inferred:** only the email/SMS script is applied by this filter, unless the namespace prefix happens to include the word (the default embedded name is the project default namespace plus folders plus filename; the project name does contain `Legitimate`). If the resource name is `Neo.LegitimateLicences.Web.DBUp.Scripts...`, then **all three** match because `Neo.LegitimateLicences` is in the resource name. That depends on the root namespace. **Requires Confirmation** by inspecting the compiled manifest. The filter was clearly written to look for `Legitimate`, and one script's filename also contains that word.

The script uses `CREATE OR ALTER PROCEDURE`, so it targets a SQL Server version that supports that syntax.

## Security notes for operators

These are observations, not a hardening guide.

- Membership passwords are configured as clear text.
- SQL credentials and an API key are in source control.
- `debug` is true and custom errors are off in the committed web config.
- The API key comparison is a straight string equality on a header.
- SignalR is allowed to anonymous users at the `SignalR` path.
- Sentry scope in `Global.asax.cs` can attach the current user when a DSN is set.

## Integrations

| System | How the code reaches it |
|---|---|
| SQL Server | EF and raw `SqlCommand` / `ExecuteXmlReader` |
| AFX services | `AfxServicesPath` and `.svc` files |
| Static CDN | `AfxCdnPath` and project `Neo.LegitimateLicences.Cdn` |
| Sentry | SDK 3.33.1, DSN app setting |
| SignalR | Web host and the Windows-service client |
| Desktop | Custom protocol and the API |
| PDF | WebSupergoo key setting (disabled-on-test name) |
| Fingerprints | `DPUruNet` when the flag is true |
| Email and SMS | Rows in `Crm.Messages`. The sender setting is `SenderEmailAddress`. `WIndowServiceInstall` contains `Interop.Clickatell.dll`. No C# call site and no SMTP host were found in the source that was read. How a `Crm.Messages` row becomes an SMS is **Inferred** to involve that interop assembly inside the service binaries |
| DevExpress | Report UI 19.1 |
| Azure DevOps | `azure-pipelines.yml` only. No Bitbucket Pipelines file |

`SystemUsername` is `info@neoafrica.com`. That is the only company domain found in app settings.

## Build, test, and deploy

- Build: Visual Studio / MSBuild of the solution files, NuGet restore. The pipeline also web-packages to `WebApp.zip` with IIS app path `Default Web Site`.
- Tests: none in the tree.
- Database change on boot: DbUp, as above. There is no checked-in full schema creation script. `EnsureDatabase` only creates an empty database if it is missing. The real schema is assumed to exist. **Inferred** from the size of the edmx versus three delta scripts.
- Windows service: `WIndowServiceInstall/install.bat` calls `installutil` on `C:\Installs\LegitimateLicense\Neo.LegitimateLicences.WinService.exe`. `uninstall.bat` is in the same folder.
- Assembly refresh: `RefreshAssembliesFromNeoAfxMaster.bat` (machine-local `xcopy`, then deletes `Neo.LegitimateLicences.Web\bin\*.*`).

## Repository layout notes

- Folder `Neo.LegitimateLicences.Purifaction` holds project `Neo.LegitimateLicences.Purification.csproj`.
- `Neo.LegitimateLicences.Verifier` is nested one directory deeper than the other projects.
- `Assemblies` holds shared binaries (not enumerated file by file).
- Vendored SweetAlert2 under the CDN project includes its own GitHub markdown. That is third-party content, not product documentation.
- No `sonar-project.properties` in this commit. The task-creation scan did not claim one for this repo.

## Local tree that is not this revision

On the inspection machine, a non-Git directory `LegitimateLicences_QA` contains files this commit does not: `README.md`, `Environment_Readiness_Checklist.md`, `docs/EhailingProviders.md`, YAML files named `NeoAfricaDevelopment*.yaml`, test projects, and a providers-registry project. Do not merge that description into this revision's behaviour without confirming it was committed somewhere else.
