# Which Service Operations Can Half Your Users Call?

> PeopleSoft service operation access is granted by permission list (PSAUTHWS) and rides roles out to users. A single broadly-held role can expose a web service to hundreds of accounts. How to trace the chain, the gotchas that undercount it, and a faster way.

---

LLMS index: [llms.txt](/llms.txt)

---

Ask a PeopleSoft security administrator "who can call this web service?" and you will usually get a pause. There is no delivered screen that ranks service operations by how many users can reach them. The grant is buried one level below where anyone looks: a service operation is authorized to a *permission list*, and permission lists ride *roles* out to users. A single broadly-held role can quietly put a data-moving web service in reach of hundreds of accounts that have no business calling it.

<!-- FINDING: fill from live security-ws-access run once test server recovers, e.g.:
"Here is a finding from a delivered Campus Solutions 9.2 image: the service
operation `X` is callable by N unlocked users, through a single permission
list carried on a role assigned across the campus." -->

## Why This Is a Security Problem, Not a Trivia Question

A service operation is not a report. Depending on its handler, calling one can read data straight out of the application tables or write data into them. The `SOAPToCI` family of operations, for example, turns any authorized caller into a Component Interface client that can create and update records over HTTP. An operation exposed to a wide audience is a wide write surface.

The exposure is easy to create and hard to see:

- Web service access is granted at the **permission list** level, but people think about access at the **role** level. The permission list that grants a sensitive operation often lives on a role that also grants something everyone needs, so it gets assigned broadly.
- There is no delivered inquiry that answers "rank my service operations by user reach." You would have to write the join yourself, and most people never do.
- Non-production environments can have unusually broad access because they inherit delivered grants and then accumulate extra user assignments, so the widest-open operations are often the ones nobody configured on purpose.

## The Security Chain

Service operation access flows through a fixed chain, the same shape as component access but with a different starting table:

1. **`PSAUTHWS`** — maps each service operation (`IB_OPERATIONNAME`) to the permission lists (`CLASSID`) that grant access to it
2. **`PSROLECLASS`** — which roles carry each permission list
3. **`PSROLEUSER`** — which users hold each role
4. **`PSOPRDEFN`** — the user accounts, including `ACCTLOCK`

A user can call a service operation if *any* permission list on *any* of their roles has a `PSAUTHWS` row for it.

## The Query

To rank service operations by the number of unlocked users who can reach them:

```sql
SELECT   ws.IB_OPERATIONNAME,
         COUNT(DISTINCT ru.ROLEUSER) AS user_count
FROM     PSAUTHWS    ws
JOIN     PSROLECLASS rc ON rc.CLASSID  = ws.CLASSID
JOIN     PSROLEUSER  ru ON ru.ROLENAME = rc.ROLENAME
JOIN     PSOPRDEFN   o  ON o.OPRID     = ru.ROLEUSER
WHERE    o.ACCTLOCK = 0
GROUP BY ws.IB_OPERATIONNAME
ORDER BY user_count DESC;
```

The operations at the top of that list are the ones to justify first. For any single operation, drop the `GROUP BY` and add `WHERE ws.IB_OPERATIONNAME = 'YOUR_OP'` to see the exact roles and users behind the count.

## Four Gotchas That Undercount Your Exposure

The join above is the honest starting point, but every mistake here makes access look *smaller* than it is, which is the dangerous direction to be wrong in.

1. **Locked accounts cut both ways.** `ACCTLOCK = 0` answers "who can call it today." Drop the filter and you answer "who is provisioned," which is the right question for a cleanup audit. Decide which one you are running; they give different numbers.

2. **Dynamic roles.** `PSROLEUSER` lists who holds a role *right now*. If a role is populated by a rule or query, the membership can change on the next refresh, so today's user count is a floor, not a ceiling.

3. **The count is only half the risk.** A service operation reachable by 300 users is a footnote if its handler does nothing sensitive, and a serious finding if it maps to a Component Interface that writes to `PS_JOB`. User reach tells you *how many* doors exist; you still have to look at *what is behind each one* by tracing the operation's handler and routings.

## The Faster Way

Writing the four-table join once is fine. Running it across every environment, keeping the primary-permission-list path in it, and re-checking after each refresh is the part that does not happen.

[psLens](https://pslens.com) ships this as the [Web Service Operation Access Audit](/docs/reports/integration-broker/security-ws-access/) report. It traces `PSAUTHWS` → `PSROLECLASS` → `PSROLEUSER` → `PSOPRDEFN` for every operation and sorts by unlocked user count, so the widest-open operations are at the top, exported as audit-ready Markdown with each operation linked to its detail page. The [SOAP to CI Access Audit](/docs/reports/security/security-soap-to-ci-access/) does the same for the `WEBLIB_SOAPTOCI` write path and lists the Component Interfaces each user can actually reach.

The same chain is browsable in either direction: start from a service operation and see the permission lists, roles, and users behind it, or start from a user and see every web service they can call. No SQL access required, which also means the analyst asking the question does not need the database account that usually comes with it. You can read [the exact output these reports produce](/docs/reports/sample-output/) before installing anything.

---

### Subscribe for Updates

If you found this article helpful, subscribe to get notified of new PeopleSoft technical notes and psLens updates.











<div class="card bg-light border-0 border-start border-primary border-3 my-4 shadow-sm">
    <div class="card-body p-3">
        <form action="https://subscribe.cedarhillsgroup.com/subscribe" method="POST" class="row align-items-center g-3">
            
            <input type="text" name="website" style="position: absolute; left: -5000px;" tabindex="-1"
                autocomplete="off">
            <input type="hidden" name="redirect_to" value="https://pslens.com/subscribed/">
            <input type="hidden" name="topic_id" value="pslens">

            <div class="col-lg-5">
                <h6 class="mb-1 text-dark fw-bold">Stay Updated</h6>
                <p class="mb-0 text-muted small" style="font-size: 0.85rem;">Receive new articles directly in your inbox.</p>
            </div>

            <div class="col-md-6 col-lg-3">
                <input type="text" name="first_name" placeholder="First Name (optional)"
                    class="form-control form-control-sm">
            </div>

            <div class="col-md-6 col-lg-3">
                <input type="email" name="email" required placeholder="Email Address"
                    class="form-control form-control-sm">
            </div>

            <div class="col-12 col-lg-1">
                <button type="submit" class="btn btn-primary btn-sm w-100">Join</button>
            </div>
        </form>
    </div>
</div>
