-
Notifications
You must be signed in to change notification settings - Fork 22
[Security Advisory Patches Portal]: Initial Release #677
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
Open
Suwagath
wants to merge
59
commits into
wso2-open-operations:main
Choose a base branch
from
Suwagath:restore/fork-main-before-upstream-reset
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 57 commits
Commits
Show all changes
59 commits
Select commit
Hold shift + click to select a range
4b60056
Initialize security advisory patches portal with backend and frontend…
Suwagath e2a9c06
Remove CORS configuration from service.bal
Suwagath c33913d
Remove startup log message in service initialization
Suwagath ed4c37a
Update title and copyright comments in HTML and CSS files; fix header…
Suwagath 5a1acbc
Update FileShareItem type reference in directory content resource
Suwagath 5898e8c
Enhance path validation in file retrieval and directory listing resou…
Suwagath 257e59f
Refactor file content fetching in FileViewer component to prevent sta…
Suwagath 2b5e9b1
Refactor content type determination in file storage utility to use a …
Suwagath 0ece21d
Enhance FileViewer component to improve error handling and loading st…
Suwagath 2127f2d
Update APP_NAME default value in config to 'Security Advisory Patches…
Suwagath 5212cfe
Improve error handling in index.tsx by adding a check for the root el…
Suwagath 4da67a6
Normalize path handling in fileSlice: trim whitespace, remove duplica…
Suwagath 8d45a0e
Update statusCodesToRetry in APIService to include duplicate 401 as p…
Suwagath d37b2c0
Enhance APIService initialization by ensuring interceptors are attach…
Suwagath 2e84340
Remove redundant header initialization in APIService request intercep…
Suwagath a2b18b2
Refactor path encoding in file storage utility to use FOLDER_DELIMITE…
Suwagath 15bed95
Enhance APIService by implementing a mechanism to manage the authenti…
Suwagath 31717c9
Refactor UnhealthyResponse type by removing the details field and sim…
Suwagath 7384958
Refactor APIService to ensure a single instance is created, attach re…
Suwagath 294ae43
Remove CORS configuration
Suwagath 19e0c7a
Change HTTP listener port from 8080 to 9090
Suwagath f41d302
Refactor FileViewer component to manage object URL cleanup more effec…
Suwagath fd8f037
Merge branch 'main' of https://github.com/Suwagath/cs-tools
Suwagath e23b70f
Update security-advisory-patches-portal/backend/modules/file_storage/…
Suwagath 082e8f8
Update security-advisory-patches-portal/webapp/src/context/AuthContex…
Suwagath 205d2ce
Update security-advisory-patches-portal/backend/service.bal
Suwagath cb2bfde
Update security-advisory-patches-portal/backend/modules/file_storage/…
Suwagath b570496
Update security-advisory-patches-portal/backend/modules/file_storage/…
Suwagath 0a89f3f
Update security-advisory-patches-portal/webapp/public/index.html
Suwagath 9fc91c5
Update security-advisory-patches-portal/webapp/package.json
Suwagath 004e064
Refactor code to remove trailing whitespace and ensure consistent for…
Suwagath 4f03307
Update Config.toml.local
Suwagath b02cb4b
Add a blank line
Suwagath c0c6fe9
Update dependencies and refactor navigation in Header component
Suwagath 2a97148
Update path validation regex in constants.bal to include URL-encoded …
Suwagath 78a8982
Refactor URL handling functions to use standard URL encoding and impr…
Suwagath f85ffb0
Merge branch 'wso2-open-operations:main' into main
Suwagath 5e2099b
Merge branch 'wso2-open-operations:main' into main
Suwagath e1ebb7d
Merge branch 'wso2-open-operations:main' into main
Suwagath 186f07a
Merge branch 'wso2-open-operations:main' into main
Suwagath 883b7f2
Move security-advisory-patches-portal under apps/
Suwagath 7cb0964
Merge branch 'wso2-open-operations:main' into main
Suwagath 6162aea
docs(security-advisory-patches-portal): add README user and developer…
Suwagath 8d0f571
feat(security-advisory-patches-portal): deep links, URL encoding, doc…
Suwagath d087916
chore(security-advisory-patches-portal): remove docs folder from repo
Suwagath 6f52a3b
Merge branch 'wso2-open-operations:main' into main
Suwagath 43de3c3
feat(security-advisory-patches-portal): PDF-only SPA and simplified r…
Suwagath 6745633
chore(security-advisory-patches-portal): cleanup, path fixes, and API…
Suwagath 694c56a
feat(security-advisory-patches-portal): JWT gate for /file and templa…
Suwagath 691d675
Update README with role descriptions and CORS info
Suwagath 33495e3
chore(security-advisory-patches-portal): scope local dev to localhost…
Suwagath 7a13646
fix(security-advisory-patches-portal): tighten path validation and Co…
Suwagath 4de82ea
chore(security-advisory-patches-portal): avoid PII in JWT forbidden logs
Suwagath f95acfe
chore(security-advisory-patches-portal): update comment for file shar…
Suwagath 792394b
fix(patches-portal): validate paths via file_storage and harden auth …
Suwagath 478c8b7
Update apps/security-advisory-patches-portal/README.md
Suwagath 23db76f
chore(patches-portal): address PR review (copyright 2026, const style)
Suwagath 6b2a8fc
Remove CORS configuration from service.bal
Suwagath 4d0ef87
Merge branch 'wso2-open-operations:main' into restore/fork-main-befor…
Suwagath File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| # Security Advisory Patches Portal — User & Developer Guide | ||
|
|
||
| This guide explains how the **Security Advisory Patches Portal** fits together: advisory **PDFs** live in an **Azure Files** share, a **Ballerina** backend streams bytes by path, and a minimal **React** app signs users in with **Asgardeo**. Any URL whose path **ends with `.pdf`** loads that file; otherwise you see **404**. | ||
|
|
||
| --- | ||
|
|
||
| ## 1. What the portal does | ||
|
|
||
| | Role | What you do | | ||
| |------|-------------| | ||
| | **Content publisher** | Upload or update PDFs (and folders) in the configured **Azure File Share** (Azure Portal, Storage Explorer, AzCopy, automation). | | ||
| | **End user (e.g. customer)** | Sign in with **Asgardeo** and open the **PDF link** you were sent (path ends with **`.pdf`**). | | ||
| | **Developer / operator** | Configure Azure credentials and share name, run or deploy the backend and webapp, tune `config.js`. | | ||
|
|
||
| The webapp does **not** upload files to Azure. Publishing is always done **outside** via Azure Portal. | ||
|
|
||
| --- | ||
|
|
||
| ## 2. Architecture (high level) | ||
|
|
||
| ```text | ||
| ┌─────────────────┐ HTTPS + Bearer token ┌──────────────────┐ | ||
| │ React webapp │ ─────────────────────────────► │ Ballerina API │ | ||
| │ (Asgardeo OIDC)│ ◄───────────────────────────── │ (port 9090) │ | ||
| └────────┬────────┘ PDF blob └────────┬─────────┘ | ||
| │ │ | ||
| │ window.config (config.js) │ account key or SAS | ||
| │ BACKEND_BASE_URL, Asgardeo URLs ▼ | ||
| │ ┌──────────────────┐ | ||
| └────────────────────────────────────────► │ Azure File Share│ | ||
| └──────────────────┘ | ||
| ``` | ||
|
|
||
| - **Frontend**: Reads runtime settings from `public/config.js`. After sign-in, paths ending in **`.pdf`** map to `GET /file?path=…` (share-relative path); the PDF is shown in-page. Other paths show **404** unless you are on **`/`** (short help text). | ||
| - **Backend**: Uses `ballerinax/azure_storage_service.files` to **download file bytes only**. It performs a **health check** against the file share at startup. | ||
|
|
||
| --- | ||
|
|
||
| ## 3. Guide for end users | ||
|
|
||
| ### 3.1 Sign-in and opening the site | ||
|
|
||
| 1. Use the portal hostname your organization gave you (for example `https://patches.example.com/`). | ||
| 2. You are redirected to **Asgardeo** to sign in. | ||
| 3. If you land on **/** after login without having opened a PDF link first, you see a short message: open the **full URL** you received whose path ends with **`.pdf`**, or sign out and open the link from your email again. | ||
| 4. If you started from such a link before signing in, that URL is restored after login when the identity provider sends you back to the site root. | ||
|
|
||
| ### 3.2 PDF viewing only | ||
|
|
||
| - There is **no** folder browser—only the PDF viewer when the link is valid and the file exists. | ||
| - **Hyphen encoding** (legacy links): doubled `--` for literal hyphens and single `-` for spaces in a segment; segments without `--` keep `-` as-is. Prefer **`%20`** for spaces when building links. | ||
| - **Folder slugs vs Azure**: Path segments for **directories** that look like lowercase kebab-case (e.g. `security-patches`, `january`) are turned into Azure-style names (`Security Patches`, `January`) before calling the API. The **PDF file name** segment is never changed—use the exact name in the share (e.g. `WSO2-2025-3857_CVE-2025-0326.pdf`). You can also put real folder names in the URL with **`%20`** for spaces; those segments are left as-is. | ||
| - If the first URL segment is **`patches`**, it is stripped when resolving the Azure path (existing **`/patches/…`** links keep working). | ||
|
|
||
| ### 3.3 Invalid links and missing files | ||
|
|
||
| - If the path does not end with **`.pdf`**, you get a **404** page (except **`/`**, which shows the help text). | ||
| - If the path ends with **`.pdf`** but the backend cannot return the file, you also see **404**. | ||
|
|
||
| ### 3.4 Sign out | ||
|
|
||
| Use **Sign out** in the header to leave the app (behavior depends on your IdP). | ||
|
|
||
| --- | ||
|
|
||
| ## 4. Guide for content publishers (Azure File Share) | ||
|
|
||
| ### 4.1 Where files go | ||
|
|
||
| PDFs are served from **one** Azure file share (storage account name, share name, access key or SAS in Ballerina config). | ||
|
|
||
| ### 4.2 Upload or update content | ||
|
|
||
| Use Portal, Storage Explorer, AzCopy, or automation—same as any Azure Files workflow. | ||
|
|
||
| ### 4.3 Naming and path rules | ||
|
|
||
| The backend validates `path` using the same segment rules as the file-storage module (no `..`, no control characters, sane length) before calling Azure. | ||
|
|
||
| ### 4.4 When updates appear | ||
|
|
||
| The next successful fetch loads the **current** bytes from Azure (reload the page if you replaced the file). | ||
|
|
||
| --- | ||
|
|
||
| ## 5. Guide for developers | ||
|
|
||
| ### 5.1 Repository layout | ||
|
|
||
| | Path | Purpose | | ||
| |------|---------| | ||
| | `backend/` | Ballerina package `security_advisories_fileshare` — `modules/authorization` (JWT / roles), `modules/file_storage`, `GET /health`, `GET /file` | | ||
| | `webapp/` | React SPA — Asgardeo, Redux (auth only), PDF viewer | | ||
|
|
||
| ### 5.2 Prerequisites | ||
|
|
||
| - **Ballerina** `2201.12.9` (`backend/Ballerina.toml`) | ||
| - **Node.js** compatible with `react-scripts` 5 / TypeScript 4.9 | ||
|
|
||
| ### 5.3 Backend configuration | ||
|
|
||
| `Config.toml` / `Config.toml.local` follow the same layout as [`webapps/backend-template`](../../webapps/backend-template): **Azure file share** settings plus **authorization** (Asgardeo group → API access). | ||
|
|
||
| **Authorization** (required for `GET /file`; see `backend/modules/authorization/`): | ||
|
|
||
| ```toml | ||
| [security_advisories_fileshare.authorization.authorizedRoles] | ||
| securityPatchesUserRole = "<Asgardeo-group-name>" | ||
| ``` | ||
|
|
||
| - Set `securityPatchesUserRole` to the **exact** string that appears in the ID token **`groups`** array. | ||
| - For `/file`, the SPA sends the Asgardeo **ID token** on **`x-jwt-assertion`**. OIDC scopes are set in `webapp/src/config/config.ts` to **`openid`**, **`email`**, and **`groups`** so the ID token includes **`email`** and **`groups`** for `CustomJwtPayload`. | ||
| - **`GET /health`** does **not** require a JWT (liveness / probes). **`OPTIONS`** preflight is also allowed without JWT. | ||
|
|
||
| **File share** (same `security_advisories_fileshare.file_storage` tables as before). Then run: | ||
|
|
||
| ```bash | ||
| cd backend && bal run | ||
| ``` | ||
|
|
||
| Listener **9090** by default; startup fails fast if the share is unreachable. | ||
|
|
||
| ### 5.4 Backend HTTP API | ||
|
|
||
| | Method | Path | Query | Description | | ||
| |--------|------|--------|-------------| | ||
| | `GET` | `/health` | — | Liveness: file share reachable (**no** `x-jwt-assertion`) | | ||
| | `GET` | `/file` | `path` required | PDF bytes; requires **`x-jwt-assertion`** (ID token) and membership in the **`securityPatchesUserRole`** Asgardeo group; `Content-Disposition: inline` | | ||
|
|
||
| The server runs **`url:decode`** on the `path` query value (`UTF-8`) so `%2F` becomes `/` when needed. | ||
|
|
||
| Missing/invalid JWT or wrong groups → **403** / **500** as returned by the JWT interceptor; missing `path` context after auth → **400**. Invalid `path` → **400**; path valid but missing on the share (Azure **404**) → **404**; other download failures → **500**. | ||
|
|
||
| The `path` query must be the **share-relative** path Azure expects (folder names, spaces, casing). The SPA maps lowercase **kebab-case** directory segments in the URL to that shape before calling `/file`; you can also send the literal path with **`%20`** for spaces (e.g. `Security%20Patches/...`). | ||
|
|
||
| ### 5.5 Webapp configuration (`config.js`) | ||
|
|
||
| The app loads `public/config.js` before the bundle. Define `window.config` with at least: | ||
|
|
||
| | Key | Purpose | | ||
| |-----|---------| | ||
| | `APP_NAME` | Title context | | ||
| | `ASGARDEO_BASE_URL` | Asgardeo server URL | | ||
| | `ASGARDEO_CLIENT_ID` | Asgardeo client ID | | ||
| | `AUTH_SIGN_IN_REDIRECT_URL` | Post-login redirect URI (typically site root `/`) | | ||
| | `AUTH_SIGN_OUT_REDIRECT_URL` | Post-logout redirect | | ||
| | `BACKEND_BASE_URL` | API origin for `/health` and `/file` | | ||
|
|
||
| Example: | ||
|
|
||
| ```javascript | ||
| window.config = { | ||
| APP_NAME: 'Security Advisory Patches Portal', | ||
| ASGARDEO_BASE_URL: 'https://api.asgardeo.io/t/<org>', | ||
| ASGARDEO_CLIENT_ID: '<client-id>', | ||
| AUTH_SIGN_IN_REDIRECT_URL: 'http://localhost:3000/', | ||
| AUTH_SIGN_OUT_REDIRECT_URL: 'http://localhost:3000/', | ||
| BACKEND_BASE_URL: 'http://localhost:9090', | ||
| }; | ||
| ``` | ||
|
|
||
| From `webapp/`: | ||
|
|
||
| ```bash | ||
| npm install | ||
| npm start | ||
| ``` | ||
|
|
||
| Default dev server **`http://localhost:3000`**. Register that origin in your Asgardeo SPA app (redirect URLs and allowed origins as required), for example `http://localhost:3000/` with a trailing slash so it matches `AUTH_SIGN_IN_REDIRECT_URL` / `AUTH_SIGN_OUT_REDIRECT_URL` in `public/config.js`. You can also add **`http://127.0.0.1:3000/`** if you open the app via `127.0.0.1`. This repo assumes local dev uses **localhost only**, not a custom hostname in `/etc/hosts`. | ||
|
|
||
| ### 5.6 Production authentication note | ||
|
|
||
| The backend reads **`x-jwt-assertion`** (JWT decode) and checks Asgardeo **`groups`** against `authorizedRoles` (see `backend/modules/authorization/authorization.bal`). | ||
|
|
||
| ### 5.7 Troubleshooting | ||
|
|
||
| | Symptom | Things to check | | ||
| |--------|------------------| | ||
| | Backend won’t start | Share name, credentials, firewall | | ||
| | 400 on `/file` | Path fails segment validation (e.g. `..`, empty segment); or missing user context after auth | | ||
| | 403 on `/file` | User not in `securityPatchesUserRole` group; ID token missing `groups` | | ||
| | 400 "User information header not found!" | Request reached `/file` without expected auth context; verify JWT interceptor wiring and that SPA sends `x-jwt-assertion` (see `apiService.ts`) | | ||
| | 500 "Missing invoker info header" | SPA not sending `x-jwt-assertion` (see `apiService.ts`) | | ||
| | 500 "Malformed Invoker info object!" | Decode the ID token: it must include **`email`** and **`groups`**. Use scopes **`openid`**, **`email`**, **`groups`** in `webapp/src/config/config.ts`; in Asgardeo enable those attributes on the app and assign the user to a group matching `securityPatchesUserRole` | | ||
| | Blank PDF | Wrong path mapping vs Azure layout; browser blocking blob iframe | | ||
| | Auth loops | Redirect URIs match `config.js` | | ||
| | Deep link → home after login | Keep sign-in redirect at site root; the app restores **`.pdf`** links from session | | ||
|
|
||
| --- | ||
|
|
||
| ## 6. Summary | ||
|
|
||
| - **Publishers** put PDFs in **Azure Files** and distribute links whose path ends with **`.pdf`**. | ||
| - **Users** sign in with **Asgardeo** and open PDF links (path ends with **`.pdf`**). Invalid or missing files show **404**. | ||
| - **Developers** run **`bal`** + **`npm`**, configure **`config.js`**, gateway CORS, and network boundaries. | ||
|
|
||
| Code paths: `backend/service.bal`, `backend/modules/file_storage/`, `webapp/src/view/PatchesPdf/PatchesPdfPage.tsx`, `webapp/src/view/NotFound/NotFoundPage.tsx`, `webapp/src/app/AppHandler.tsx`. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| [package] | ||
| org = "wso2" | ||
| name = "security_advisories_fileshare" | ||
| version = "1.0.0" | ||
| distribution = "2201.12.9" | ||
|
|
||
| [build-options] | ||
| observabilityIncluded = true |
12 changes: 12 additions & 0 deletions
12
apps/security-advisory-patches-portal/backend/Config.toml.local
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| [security_advisories_fileshare.authorization.authorizedRoles] | ||
| # Must exactly match a value in the ID token `groups` array (see README section 5.3). | ||
| securityPatchesUserRole = "app-security-patches-admin-stg" | ||
|
|
||
| # Azure File Share Configuration | ||
| [security_advisories_fileshare.file_storage] | ||
| fileShareName = "" | ||
|
|
||
| [security_advisories_fileshare.file_storage.fileStorageConfig] | ||
| accountName = "" | ||
| accessKeyOrSAS = "" | ||
| authorizationMethod = "" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.