Security & Access Control¶
This page documents how the system authenticates users, authorises actions, and audits them — as built. Several mechanisms here are weak by modern standards; they are described plainly so support and any future hardening work start from the truth. The weaknesses are collected on Known Issues.
Login flow¶
flowchart TD
A["User enters UserID + Password"] --> B{"UserID = EDP or DLY?"}
B -->|Yes| C["Backdoor: password = time-derived key<br/>(digits of dd-MM-yyyy HH:mm:ss)<br/>no DB record — full access"]
B -->|No| D["SELECT * FROM tbMaster_User<br/>WHERE UserID = UCASE(input)"]
D --> E{"PassToChar(UserPassword) = input?"}
E -->|No| F["Reject"]
E -->|Yes| G{"RECORDID?"}
G -->|0 active| H["Login OK"]
G -->|1 first login| I["Force password change"]
G -->|2 disabled / else| J["Blocked / expired"]
H --> K["Enforce single session per station<br/>(tbMaster_Computer.UserAktif)"]
style C fill:#ffebee,stroke:#c62828
- Normal users are validated against
tbMaster_User; the stored password is run throughPassToChar(a reversible cipher, see below) and compared to the input. RECORDIDencodes account state:0active,1first-login (force change),2disabled, otherwise expired. On success,USERLASTLOGINis updated.- Single session per station is enforced via
tbMaster_Computer.UserAktif; clearing a stuck session needs an approval (AccessCode = "RESETUSER").
Two hard-coded superuser backdoors
Accounts EDP and DLY have no database row. Their password is a
time-derived key built from digits of the current timestamp
(dd-MM-yyyy HH:mm:ss). Anyone who knows the algorithm and the server clock can log in
as a full superuser. These accounts also bypass every permission check below.
Authorisation: per-form rights¶
Authorisation is per-form (per-URL) add/edit/delete, not role-based.
flowchart LR
U["User opens a form (URL)"] --> BA["ButtonAccess(UserID, URL, ByRef Tambah, Koreksi, Hapus)"]
BA --> Q["JOIN tbMaster_UserAccess × tbMaster_Access<br/>WHERE UserID=… AND URL=…"]
Q -->|row found| R["Set Tambah/Koreksi/Hapus from bit columns"]
Q -->|no row, EDP/DLY| S["True / True / True"]
Q -->|no row, other| T["False / False / False"]
Q -->|SQL exception| FO["⚠️ True / True / True (fail-open)"]
style FO fill:#ffebee,stroke:#c62828
style S fill:#fff3e0,stroke:#fb8c00
ButtonAccess(UserID, URL, ByRef Tambah, Koreksi, Hapus)joinstbMaster_UserAccess×tbMaster_AccessonAccessCodefor the given user + form URL, and returns the three permission bits (Tambah=add,Koreksi=edit,Hapus=delete;Baca=read gates visibility).- No grant row →
EDP/DLYget full access; everyone else gets none. - Menu visibility is gated separately by
HAKFUNC.UserAccessMenu(the menu tree is filtered per user, exceptEDP/DLYwho see everything); the button strip usesAccessCode = 'SHOWBUTTON'.
ButtonAccess fails open
On any SQL exception, ButtonAccess returns True/True/True — granting add, edit,
and delete. A transient DB error therefore elevates permissions rather than denying
them. This is the opposite of fail-safe.
The access tables¶
| Table | Holds |
|---|---|
tbMaster_Access |
Form/menu catalogue: AccessGroup+AccessCode, AccessName, URL, permission flags Baca/Tambah/Koreksi/Hapus, tree RootID. |
tbMaster_UserAccess |
Per-user grants: UserID+AccessGroup+AccessCode, with Baca/Tambah/Koreksi/Hapus bits. |
tbMaster_User |
The user record: USERID, USERPASSWORD (ciphered), USERLEVEL, STATION, RECORDID (state), USERLASTLOGIN. |
Password & credential storage¶
Passwords use a reversible cipher, not a hash
Convertion.PassToChar / CharToPass is a Caesar-style per-character shift
(nTambah = 57) plus StrReverse. It is fully reversible — anyone with the stored
value (or the algorithm) can recover the plaintext. The same cipher also protects the
SQL Server credentials in the Windows Registry, so DB username/password are recoverable
too. Neither user passwords nor DB credentials are truly secret. Modern practice would be
bcrypt/PBKDF2 for user passwords and OS-level secret storage for credentials.
Audit trail¶
Mutations and access events are logged to AuditTrail_UserAccess via the FrameWork's
shared AuditTrail(Tipe) routine.
| Column | Holds |
|---|---|
…KodeCabang |
Branch |
…Userid |
Who |
…ActionDate |
When |
…Application |
Which app |
…Modul |
Which module/form |
…Action |
Login, Open Menu, or Insert/Edit/Hapus (Tipe ½/3) |
…Description |
Details |
…IP |
Workstation IP |
MenuUtama writes a Login row on sign-in and an Open Menu row when a menu/EXE is
opened; the typed data-access SaveData paths write Insert/Edit/Hapus rows. Note the
INSERT is string-concatenated like the rest of the data layer.
Licensing¶
Startup runs a licence/expiry gate (for non-POS apps): it reads an "Expired Date" from
REF_SYSTEM, and if expired calls the cloud licence API at
https://license.smartsoft.co.id/license?client_id=<branch>. On failure it can blank the
server name (blocking use) and pop the settings dialog. CD-key validation lives in
MyLib.CdKey.
Summary of security-relevant behaviours¶
| Behaviour | Risk |
|---|---|
EDP / DLY time-based backdoor superusers |
Undocumented full-access accounts. |
ButtonAccess fails open on SQL error |
DB hiccup can elevate privileges. |
| Reversible-cipher passwords and DB credentials | Both recoverable from stored values. |
| String-concatenated SQL everywhere | SQL-injection exposure (see Data Access). |
| Credentials embedded in the connection string | Cleartext in memory / registry-obfuscated only. |
See Known Issues & Risks for the consolidated list and context.