Skip to content

Build1 publisher3 min readPublished

Cloning a model profile with CPYUSRPRF replaces the copy-her-authority ticket on IBM i

A dev.to write-up drains a JSON inbox with an SQL cursor over IFS_OBJECT_STATISTICS, branches on two QSYS2.USER_INFO counts and hands the profile clone to CL. The eight declared request fields include no approver.

The Engineer · Build desk

Illustration accompanying Cloning a model profile with CPYUSRPRF replaces the copy-her-authority ticket on IBM i

What happened

  • A dev.to write-up describes an IBM i access provisioning job that runs on RPGLE, embedded SQL and the IFS alone: no middleware, no extra servers, no message queue.
  • An intake form writes a JSON request into /home/access/inbox and a scheduled RPGLE job drains it each pass, with the filesystem used in place of a trigger or a data queue.
  • The job lists the inbox with an SQL cursor over the QSYS2.IFS_OBJECT_STATISTICS table function filtered to object_type = '*STMF', instead of walking directory entries.
  • Two COUNT(*) checks against the QSYS2.USER_INFO view decide between create, refresh and reject, and a CL wrapper around CPYUSRPRF does the profile clone.
  • New accounts get a randomly generated password applied with CHGUSRPRF and PWDEXP(*YES), executed as a concatenated CL string through QSYS2.QCMDEXC.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • capability Clone-from-model moves the security question from every new hire to a small set of model profiles, and one stale model quietly propagates its accumulated authorities to everyone cloned from it.
  • exposure The generated password is concatenated into a CHGUSRPRF command string before it runs, so anywhere that string is captured, the cleartext goes with it.
  • constraint The audit event can answer which model was cloned and when, but with no field for approval evidence in the request contract, that evidence has to be held by whatever system writes the JSON.
  • cost Shops below IBM i 7.2 with PTFs pay for a JSON parser before any of this is reachable, since DATA-INTO with an external parser is what removes the hand-rolled token walking.

The model profile is the specification. The write-up's framing is that you never have to define what access means in the abstract; you say make user A look like user B, and the clone operation copies the model's authorities [5]. That moves the hard part out of the program and into the choice of model. Every authority the model picked up over five years comes with it, including the ones nobody remembers granting.

Step ordering is where I would look first. Archiving the source file is step 8, after the profile clone at step 4, the entitlement rows at step 5 and the audit event at step 6 [7]. A pass that overlaps the previous one can read a file that has already been provisioned, because the file is only removed at the end. The three-way branch on the two USER_INFO counts absorbs most of that: a target that now exists routes into the refresh branch instead of create [15].

The options string decides how strict the parse is: `doc=file case=convert countprefix=num_ allowmissing=yes allowextra=yes trim=none` [12]. So `allowextra=yes` lets the intake form add fields the RPG program has never heard of, and `allowmissing=yes` lets it omit them. A request with no `targetUserId` parses cleanly and arrives as blanks. Validation lands entirely on the two existence checks against QSYS2.USER_INFO [15]. "Declare the contract once, as a qualified data structure, and let the parser fill it," the post says [13].

Two adoption costs sit in the code. `%PARSER('YAJL/YAJLINTO')` means the YAJL service program has to be installed in a library of that name [12], and the post describes it only as the YAJL-based parser service program [10]. DATA-INTO itself needs IBM i 7.2 with PTFs, or 7.3 and later [10].

Verification is a count. After the CHGUSRPRF string runs through QSYS2.QCMDEXC, the program selects `COUNT(*)` from USER_INFO for the target name and writes the history record only once that comes back [17][18]. "Don't just trust that the CL call succeeded," the post says [18]. The count confirms a profile with that name exists. The target's authorities are never compared against the model's. The published excerpt breaks off mid-sentence at the history record write [18].

On a shared IFS mount, the check that stops two partitions claiming the same request is a comparison of `requestDoc.targetSystem` against `CURRENT SERVER`, skipping any file addressed elsewhere [14]. The write-up offers that as a tip [14].

The declared structure has eight fields: requestDate, targetUserId, targetUserName, targetUserEmail, modelUserId, targetSystem, siteCode and managerEmail [11]. None of them is an approver [1]. managerEmail is a notification address, used at step 7 to mail the requester and the manager [7]. The complaint the piece opens with is that six months later nobody can say who approved what, or when [2]; the flow as drawn begins when the intake system writes the JSON file [6], so the approval evidence lives there.

What to watch

  • Whether the rest of the excerpt gets published. It breaks off at the history record write, and the zero-confirmation path is still unpublished.
  • Documentation of the intake side. The approver's identity has to arrive inside the JSON file to reach the audit event.
  • For shops below IBM i 7.2 with PTFs, whether a parser path turns up at all.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories