# 00 — Technical Architecture Deep-Dive (obgy clinic system)

Audit scope: `/home/amrtechogate/public_html/obgy` (referred to below as `obgy/`).
Audience: engineering input for the management migration report (legacy PHP → Laravel + Angular ERP).

---

## 1. System Overview

A custom, in-house PHP framework self-described as **"awframework from aw.inc"** (header comment present in nearly every controller; artifacts date to ~2015, e.g. the seed admin user registered `2015-01-25`). No standard framework, no Composer, no autoloading, no version control artifacts, no tests.

Four sub-applications share one MySQL database and one shared library directory:

| Sub-app | Path | Controllers | Purpose |
|---|---|---|---|
| core | `obgy/core/` | 70 PHP files, ~37,878 lines total | Main clinic: patients, visits, clinical sheets, financial reports, settings, backups, mobile API |
| pharmacy | `obgy/pharmacy/` | 6 (`buys, drugs, receipt, store, stuff, test`) | Pharmacy/stock |
| board | `obgy/board/` | 4 (`requests, sessions, stuff, test`) | Teaching board |
| screen | `obgy/screen/` | 1 (`index.php`) | Waiting-room queue display; directly `require`s `core/controllers/imp/` |

Shared:
- `obgy/_library/` — `db_main/rb.php` (ORM), `login_system/`, `php-jwt-master/`, `smarty_plg/`, `plugins/` (Breadcrumbs, classupload, PHPMailer).
- `obgy/_public/aw_config.php` (template config) + `api_config.php`.
- `obgy/upload/` — 3.2 GB total; `upload/sonar` alone is 3.1 GB (1,846 files). Inside web root.
- `obgy/_db/` — three full SQL dumps in web root (`obgy_12-7-2024.sql`, `obgy_app_20-04-2024.sql`, `obgy_app_22-12-2021.sql`).

Database: `amrtechogate_obgy` — **312 tables** (307 InnoDB, 5 MyISAM — including `awusers`).

Web server config: root `.htaccess` sets `DirectoryIndex core/controllers/index.php`, error documents, `Options -Indexes`, and `RewriteEngine off` (no rewriting/no protective rewrite rules).

---

## 2. The "aw framework"

### 2.1 Routing — direct file access + `?ac=` dispatcher

There is **no front controller**. Every screen is a standalone file hit directly, e.g. `/obgy/core/controllers/patients.php?ac=search`.

Dispatcher — `obgy/core/controllers/imp/_imp.php` (entire routing layer, 23 lines):

```php
$action = filter_input(INPUT_GET, 'ac');
if (!$action) { $action = 'index'; }
$call_func = new Controllers();
if (method_exists($call_func, $action)) {
    $call_func->$action();
} else {
    $call_func->index();
}
```

Implications:
- **Every `public` method of the controller class is URL-invokable** — there is no action allow-list, no HTTP-verb discrimination, no CSRF token anywhere.
- All 81 controller files define a class with the *same name* `Controllers` (works only because each file is its own entry point).

### 2.2 Controller pattern

Each controller (`obgy/core/controllers/_controll.php` is the canonical example) repeats ~60 lines of boilerplate in `__construct()`:
1. `require_once("../public/aw_config.php")` → DB connect + Smarty instance.
2. `require_once("imp/_autho.php")` → static `autho` class (login + RBAC check).
3. Breadcrumbs plugin, sets `$active[]` menu-highlight array.
4. `require_once` of `_header.php`, `_sidebar.php`, `_leftsidebar.php`, `_footer.php` — each defines a static class (`header::headershow()`, `sidebar::sidebarshow()`, etc.).
5. `__destruct()` calls `R::close()`.

Each action method follows the template:
```php
autho::checkautho($this->hosturl);                                   // login check
autho::checkauthoize($this->controllname, $this->hosturl, $_SESSION['role_id']);  // RBAC
// ... RedBean queries, $this->smarty->assign(...)
header::headershow($this->smarty, $this->hosturl);
sidebar::sidebarshow($this->smarty, $this->hosturl, $this->active);
$this->smarty->display('xxx/yyy.html');
leftsidebar::leftsidebarshow(...); footer::footershow(...);
```

### 2.3 View binding

- **Smarty 3.1.11** (`const SMARTY_VERSION = 'Smarty-3.1.11'` in `obgy/_library/smarty_plg/libs/Smarty.class.php`; released 2012). Plus `SmartyPaginate.class.php` and `modifier.mb_truncate.php`.
- Templates in each sub-app's `views/`, compiled to `temp/`. Pages are rendered piecemeal (head → header → sidebar → content template → left sidebar → footer), all server-side.
- No default output escaping configured — XSS exposure depends on per-template discipline.

### 2.4 Permission model (`awcontroll` / `awrole*`)

A genuinely well-designed, fine-grained DB-driven RBAC:

| Table | Role |
|---|---|
| `awcontroll` | Registered screen/controller (`name`, `label`) |
| `awcontrollprop` | Action within a screen (`proplabel` = the `?ac=` value, `checkval` = default allow) |
| `awrole` | Roles |
| `awrolecontrollprop` | role × action permission (`checkval`) |
| `awmenu` / `awrolemenu` | Hierarchical menu per role (sidebar built at runtime from `awmenu`) |
| `awrolebtn` | UI button visibility per role |
| `awusers.role_id` | Role assignment |

Enforcement — `obgy/core/controllers/imp/_autho.php`:
- `checkautho()` instantiates `Login` (`_library/login_system/classes/Login.php`) and redirects to login if `isUserLoggedIn()` is false (session-based).
- `checkauthoize()` resolves controller name → `awcontroll` → `awcontrollprop` for current `?ac=` → checks `awrolecontrollprop` for `$_SESSION['role_id']`, falling back to the prop's default `checkval`; redirects to `error.php?ac=autho` on denial.

**Weakness:** enforcement is *opt-in per method*. Several actions skip `checkauthoize` (e.g. `index.php::onesetup()` — a destructive cleanup that trashes menus/roles — only checks login), and `mobileservices.php` has **all auth checks commented out** (see §4).

---

## 3. Data Layer

- **ORM: RedBeanPHP 4.3** — `obgy/_library/db_main/rb.php`, single 12,380-line file; `const C_REDBEANPHP_VERSION = '4.3'` (line 9759). Circa 2016; current is 5.x.
- Bootstrap in `obgy/_public/aw_config.php` (duplicated into each sub-app's `public/aw_config.php`):

```php
R::setup('mysql:host=localhost;dbname=amrtechogate_obgy', 'amrtechogate_obgy', '53i#WRqAw8r121618');
R::exec('SET NAMES latin1');
R::freeze(FALSE);
```

Notable:
- **`R::freeze(FALSE)` in production** — RedBean "fluid mode": the ORM may CREATE/ALTER tables and columns at runtime when new bean properties are stored. Schema integrity depends on runtime behavior.
- **`SET NAMES latin1`** against utf8 tables storing Arabic — classic double-encoding setup. Data migration to Laravel must transcode carefully or Arabic text will mojibake.
- Query style is a mix:
  - Safe parameterized ORM calls: `R::find('visits', 'visitdate = ? ...', [...])`, `R::dispense/store/trash`, `R::begin/commit/rollback` (transactions used for composite inserts — but no-ops on the 5 MyISAM tables, which include `awusers`).
  - **Raw SQL with string concatenation** via `R::getAll/getRow/exec` — pervasive (see §5.1).

---

## 4. Auth & API

### 4.1 Login system
`obgy/_library/login_system/classes/Login.php` (+ `Registration.php`) — the open-source "panique/php-login" pattern:
- PDO with **prepared statements** throughout.
- **bcrypt** via `password_hash(..., PASSWORD_DEFAULT, ['cost' => HASH_COST_FACTOR])` (cost 10 in config), `password_verify`, and `password_needs_rehash` auto-upgrade (Login.php lines 286-340).
- PHP sessions (`session_start()`), `session_destroy()` on logout; remember-me cookie keyed by hardcoded `COOKIE_SECRET_KEY` in `aw_config.php` (2-week lifetime).
- Failed-login throttling via `awusers.user_failed_logins` / `user_last_failed_login`.

`awusers` schema (confirmed in `/home/amrtechogate/public_html/obgy/_db/obgy_12-7-2024.sql`, line 4471): `user_password_hash varchar(255)` storing `$2y$10$...` bcrypt hashes — **passwords are NOT stored in plaintext or MD5**. Engine: **MyISAM**.

Login flow controller: `obgy/core/controllers/login.php` (also contains `deviceId()`/`serialcheck()` — a homegrown licensing check).

### 4.2 Mobile / external API
- `obgy/core/controllers/mobileservices.php` (671 lines): JSON endpoints to add/update patients, etc. `header("Access-Control-Allow-Origin: *")` at top; **every `autho::checkautho` / `checkauthoize` call is commented out** — the endpoints are unauthenticated and CORS-open. Patient passwords are at least bcrypt-hashed on create. Also performs ERP sync via cURL (`curlAddClient`/`curlUpdateClient` → `HOST_URL_ERP . '/controllers/clientControllerAjax.php'`) with no auth token visible.
- `obgy/_public/api_config.php`: separate bootstrap that connects to the same DB, sets `putenv("api_key=...")`, `api_user=webApi8`, `api_password=ey8yrw4r` **hardcoded**, and turns `display_errors` **ON** (line 4). **No file inside `obgy/` references `api_config.php`** — it appears to serve external/SMS/investigation integrations (`$hosturlApi = 'http://api.gt4it.com'`, plain HTTP).
- `obgy/_library/php-jwt-master/` (Firebase php-jwt): **never used** — `grep` finds zero `JWT::encode/decode` or `php-jwt-master` references in any sub-app code. Dead dependency; there is no token-based API auth anywhere.

---

## 5. Security Posture (evidence)

### 5.1 SQL injection — CONFIRMED, widespread (CRITICAL)
Sampled controllers, direct string concatenation of user input into SQL executed through `R::getAll/getRow/exec`:

- `obgy/core/controllers/patients.php` (~lines 311-419): POST fields (`search`, `husbandname`, `address`, `phone`, `mobile`, `age`, `nationalid`, job titles) concatenated into `LIKE '%$input%'` clauses (`$q1..$q8, $qW`) then `R::getAll($query)`. Sanitizing `clean()` calls are present but **commented out**.
- `obgy/core/controllers/financialreport.php` (`commonSearch2`/`executeSql`, lines ~60-98): `" AND patients.address LIKE '%$address%'"`, `' and visits.patientid = ' . $patientId`, dates interpolated into the WHERE clause.
- `obgy/core/controllers/visits.php` (lines 282, 309, 650, 788, 1133+): `R::getRow('SELECT * FROM patients WHERE id = "' . $visit->patientid . '"')` etc.
- `obgy/core/controllers/sonar.php` line 243: `R::exec('UPDATE sonar SET tempdelete = 1 WHERE id = ' . $id)`.
- `obgy/core/controllers/index.php` lines 82-121: `R::getRow("SELECT * FROM patients WHERE id = $visit->patientid")`, detections by interpolated id.
- `obgy/core/controllers/_header.php`: `R::getRow('select * from awusers where user_id  = ' . $_SESSION['user_id'])` (session-sourced, lower risk, same pattern).

Combined with the unauthenticated `mobileservices.php`, parts of this surface are reachable **pre-auth**.

### 5.2 Other findings
| Finding | Evidence | Severity |
|---|---|---|
| Unauthenticated mobile API, CORS `*` | `core/controllers/mobileservices.php` | Critical |
| No CSRF protection anywhere; every public method URL-invokable | `imp/_imp.php` dispatch pattern | High |
| Hardcoded DB credentials (`amrtechogate_obgy` / `53i#WRqAw8r121618`) duplicated in ≥5 configs | `_public/aw_config.php`, `_public/api_config.php`, `core|pharmacy|board|screen/public/aw_config.php` | High |
| Hardcoded API creds + cookie secret | `api_config.php` (api_key/user/password), `aw_config.php` `COOKIE_SECRET_KEY` | High |
| Full DB dumps (all patient data) inside web root | `core/db_backups/` — 1.6 GB, 39 daily dumps up to `opgy_2026-06-11.sql`; `_db/*.sql`; `upload/dbs/` | High |
| Backup runs `system("mysqldump --password=$db_password ...")` — password on the process command line, executed synchronously inside the homepage request | `core/controllers/index.php::takeackup()` (line ~280) | High |
| Destructive admin action without role check | `index.php::onesetup()` (login check only) | Med-High |
| `display_errors = on` in API config; SMS API over plain HTTP | `api_config.php` line 4; `aw_config.php` `$hosturlApi = 'http://api.gt4it.com'` | Medium |
| `R::freeze(FALSE)` fluid ORM in production | `aw_config.php` line 45 | Medium |
| Smarty without default escaping (XSS depends on template discipline) | Smarty 3.1.11 config | Medium |

**Strengths (to state fairly):** bcrypt password storage with rehash-on-login; PDO prepared statements in the entire login subsystem; fine-grained DB-driven RBAC per action; `filter_input()` used consistently for input retrieval; `display_errors off` in main config; `Options -Indexes`; failed-login counters; daily automated backups (existence, if not placement).

---

## 6. Tech Debt Inventory

| Item | State | Notes |
|---|---|---|
| PHP runtime | **PHP 5.6.40** on the server (`php -v`) | EOL Dec 2018 — 7+ years without security patches; blocks modern libraries |
| Smarty | 3.1.11 (2012) | Old; known CVEs in 3.1.x line |
| RedBeanPHP | 4.3 (~2016), vendored single file | No Composer/dependency management at all |
| Code duplication | ~60-line constructor boilerplate copied across 81 controllers; config duplicated per sub-app; `screen` cross-requires `core` internals | Any framework-level change = 81 edits |
| God files | `patients.php` 2,259 lines, `visits.php` 1,946, `merge.php` 1,879, `excel.php` 1,791, `gyna.php` 1,685 | |
| Upload dir | 3.2 GB in web root (`upload/sonar` 3.1 GB, 1,846 files); files read straight off the filesystem (e.g. `upload/patientfiles/{id}`), DB index table (`patientfiles`) unused | Migration & backup burden; direct-URL exposure risk |
| Backup strategy | mysqldump triggered by first homepage visit each day (synchronous `system()` call); 39 dumps / 1.6 GB accumulating in `core/db_backups` with no rotation; optional secondary path + optional Gmail email (creds stored in `programesetting` table); `core/excel_backups` 19 MB | Same-disk backups = no disaster protection; random latency for first morning user |
| Charset | `SET NAMES latin1` vs utf8 tables (Arabic content) | Major data-migration hazard |
| Timezones | `America/Los_Angeles` in `aw_config.php` vs `Africa/Cairo` set ad hoc in code and `api_config.php` | Date bugs likely |
| Dead code | php-jwt unused; commented-out auth and sanitization; stray files in controllers dir (`1713107881.html`, `err.txt`, `out.txt`, `error_log`, `sh.php`, `test.php` controllers) | |
| Engineering process | No git, no tests, no CI, no staging artifacts visible | Every change is unmeasured risk |

---

## 7. Migration-Relevant Conclusions

1. The functional/clinical breadth and the `aw*` RBAC model are worth porting conceptually (Laravel policies/permissions map cleanly onto `awcontroll`/`awcontrollprop`/`awrolecontrollprop`).
2. The hardest migration work is **data**: 312 tables, latin1/utf8 double encoding, fluid-schema drift, MyISAM stragglers, and 3.2 GB of filesystem-only patient media with partial DB indexing.
3. Regardless of migration timeline, three issues warrant immediate remediation on the legacy system: SQL injection in the search/report controllers, the open `mobileservices.php` API, and web-accessible database dumps (`core/db_backups`, `_db/`).
