03.01 Managing Users, Teams, and Roles
Add, edit, deactivate, and delete users; create teams and assign membership; create roles and bind permissions; understand how the three layers compose to control what each person can see and do.
Why this matters
SimpleRisk's access model is three layers. Users are the people who log in. Teams group users for ownership and reporting. Roles are reusable bundles of permissions assigned to users. The three are independent: identity doesn't change with role, and team membership doesn't grant permissions. Admins who collapse the layers — putting permissions on teams, or using roles as departmental groupings — end up rebuilding the access model six months later.
The discipline maps onto NIST SP 800-53's access-control family. AC-2 Account Management governs the user lifecycle. AC-3 Access Enforcement is what role and permission bindings enforce at every page render. AC-6 Least Privilege is the test you apply when deciding which permissions a role gets. The three admin pages — User Management, Team Management, Role Management — each map to one of those concerns.
Users
A user record is the prerequisite for everything else: nothing to attach a role to and nothing to put on a team without one. Users live in the user table — value is the numeric ID, username is the login, admin is the superuser flag (1 = administrator, 0 = regular user). The page is Configure → User Management, gated by the admin permission.

Adding a user
The Add Users tab is where new accounts come in. Most installs only use the SimpleRisk account type; the LDAP and SAML options only appear when the Authentication Extra is active and indicate authentication delegated to an IdP rather than a local password.
The form fields:
- Type —
SimpleRisk,LDAP, orSAML. Pick the one matching how the user will authenticate. - Full Name — display name, max 50 characters. Surfaces in dropdowns, audit-trail entries, and notifications.
- E-mail Address — must be unique and must validate as an email. Used for password resets and notifications.
- Username — the login. Must be unique. The
Use Case Sensitive Validation of Usernamesetting on the User Settings tab controls whetherjsmithandJSmithare treated as the same user; default is on. - Password / Repeat Password — only meaningful for SimpleRisk-typed users. Validated against the password policy on User Settings.
- Multi Factor Authentication — toggles TOTP MFA. If
mfa_requiredis set system-wide, the box is checked and read-only. - Require password change on login — the next sign-in prompts for a new password. Useful for handing off temporary credentials.
- Manager — single-user picker; used by review-routing when the direct owner is unavailable.
- Teams — multi-select; which teams the user belongs to. See Teams below for what membership actually does.
- Role — single-select of every role on the system. Picking a role auto-checks the permissions that role grants in the User Responsibilities tree below; the tree stays editable so you can grant or remove permissions for this user specifically. The per-user delta lives in
permission_to_user. - Grant Admin button — toggles the
adminflag. Granting Admin selects every team, locks the team multiselect, and locks every permission checkbox — admins inherit all permissions and team visibility, so role and team assignments are meaningless for them. SimpleRisk shows a warning banner to that effect.
Click Add to save. SimpleRisk shows: A new user with username "
The Administrator role (role.value = 1) is the only role SimpleRisk ships with on a fresh install. Every other role you see in the dropdown was created on this instance. Plan your role design before you onboard the second user.
Editing or deactivating a user
The Manage Users tab is the maintenance side. View Details for User is the editing entry point: pick a user, click Select, and the per-user form opens with every field editable. Same fields as Add (minus the passwords) plus three:
- Account Locked Out — toggled automatically by the failed-login policy; an admin can flip it back off without resetting the password.
- Status — read-only
EnabledorDisabled. Changed by the Enable/Disable buttons on the previous tab. - Last Login — read-only timestamp. The first thing to check when an offboarding ticket lands.
Disabling is what most offboarding tickets actually need. The Enable and Disable Users card: pick the user from the Disable user dropdown, click Disable. SimpleRisk flips enabled to 0, calls kill_sessions_of_user() to terminate active sessions, and the user can no longer sign in. Their record stays, and their attribution on every risk they submitted, owned, or commented on stays. The audit trail is intact.
We recommend disabling rather than deleting in nearly every offboarding case. The team has been telling customers this for years on the support side: deletion severs the user from every risk, mitigation, comment, and audit-trail entry that referenced them, with no undo. Disabled accounts give you the same effective result — they can't log in — without the data loss. Reach for delete only when the account was created in error.
Deleting a user
If deletion is the right answer, the Delete an Existing User card is where it happens. Pick the user, click Delete, confirm "Are you sure you want to delete this user?". SimpleRisk runs delete_value('user', $id) plus cleanup_after_delete('user'), dropping the row from user and clearing foreign-key entries from user_to_team, risk_to_additional_stakeholder, and permission_to_user. The MFA secret is dropped from user_mfa, and active sessions are terminated.
What survives: existing audit-trail entries, which keep pointing at the now-orphan user_id. What doesn't: the user's appearance in dropdowns going forward, and any junction-table associations. Risks the user owned still exist, but the Owner field can no longer resolve to a name.
The only built-in safeguard is that an admin can't delete themselves — the form rejects the attempt with "An admin can't delete itself." There's no guard against deleting the last other admin, so leaving exactly one admin in your org is risky: if that one account gets locked out, you're recovering from the database.
For a planned offboarding where you want both the audit trail intact and the leaver's risks reassigned, the pattern most customers use is: disable the user, bulk-reassign their risks via export-edit-import using the Import/Export Extra, then leave the disabled record in place.
Teams
A team is a value (numeric ID) and a name. Users join teams through the user_to_team junction table. Risks, mitigations, and tests can be tagged with one or more teams via risk_to_team, mitigation_to_team, and items_to_teams. The page is Configure → Team Management.
In Core SimpleRisk, team tags act as filters on reports and dashboards — useful for "show me the risks Finance owns" — but they don't by themselves restrict who can see what. The Team-Based Separation Extra turns the same membership into actual data segregation. Without it, every user with the relevant view permission sees every risk regardless of team.
Creating a team
The top card stacks three forms: Add new item named with an Add button, Change … to … for renaming, Delete item named with a confirm dialog. Names are capped at 50 characters.
Team creation auto-assigns the new team to every existing admin via set_all_teams_to_administrators() — admins see every team by definition. If the Organizational Hierarchy Extra is installed, the team is also assigned to the default business unit.
Assigning users to teams
The Manage users in team card is the membership editor. Pick a team; the page reloads with Available Users on the left, Team Members on the right. The arrow buttons (>>, >, <, <<) move users between the lists. Click Update to save.
Two behaviors worth knowing. The editor doesn't list admin users — they're members of every team by definition, so listing them would be noise. And you can't delete a team currently tagged on a risk while Team-Based Separation is active; SimpleRisk blocks with "Cannot delete this team, because there are risks that are currently using it." to prevent the silent disappearance of a team's risks from the views of the people who could see them. Reassign first, then delete.
The same membership data is editable from the user side: the Teams multi-select on Add Users and on View Details for User writes to the same user_to_team table. Use whichever side is convenient.
How team membership affects visibility
This is the part admins frequently misunderstand. Team membership in Core SimpleRisk does not gate what a user can see. A user with the Risk Management permission sees every risk on the system, regardless of team. Team tags are a filter dimension on reports and dashboards, not an access control.
What turns membership into segregation is the Team-Based Separation Extra. With it activated and team_separation on, the queries driving risk lists and dashboards filter risk_to_team against the user's user_to_team rows: users see only risks tagged to teams they belong to, plus untagged risks (treated as universally visible). Admins still see everything.
This explains a recurring confusion in support: "I added the user to the Finance team and they still see Engineering's risks." The answer in nearly every case is that Team-Based Separation isn't installed, and the team tag on its own does no gating.
Roles
A role has a value (numeric ID), a name, an admin flag (does this role grant superuser?), and a default flag (do new SAML/LDAP users land here under JIT provisioning?). Permissions live in role_responsibilities — one row per (role_id, responsibility_name) pair, where responsibility_name is the permission key from the permissions table. The page is Configure → Role Management.
The default roles that ship with SimpleRisk
A fresh install has exactly one seeded role: Administrator (role.value = 1, admin = 1). That's it. Every other role on a running system was created either by an admin or by an Extra at activation time.
This shapes the rollout. The first thing most admins do is build a small set of role bundles that match how they actually run access — a Risk Manager role for risk-management work, a Compliance role for assessment and framework permissions, a Read-Only role for view-only auditors. There's no shipped catalogue. Every customer's permission discipline differs, and a shipped catalogue would be wrong for every customer in slightly different ways.
The Administrator role itself can't be deleted ("Administrator Role can not be deleted.") and its admin flag is read-only.
Creating a custom role
The Add a New Role card has one field: Role Name. Names must be unique (UNIQUE index on role.name). Click Add; SimpleRisk inserts the role with no permissions, no admin flag, no default flag. The new role is selectable in the Edit Role card immediately.
If two admins try to add the same name simultaneously, the UNIQUE constraint catches the race and the second add returns "The role name already exists." rather than producing a duplicate.
Assigning permissions to a role
Pick the role from the dropdown in Edit Role. SimpleRisk fetches its current permissions via GET /api/role_responsibilities/get_responsibilities and renders them as checkboxes in the User Responsibilities widget — a tree grouped by permission group. Three controls sit above the tree:
- Default User Role — when set, this role becomes the default landing role for new SAML/LDAP users created via JIT provisioning. Only one role at a time can hold this flag.
- Grant Admin / Remove Admin — toggles the role's
adminflag. When set, every permission checkbox is auto-selected and read-only ("User responsibilities cannot be edited when the user is an admin."). Admin roles are unrestricted by definition. - Update — saves flag values and checked permissions to
role_responsibilities.
The full catalogue of permissions — what each grants, which UI section it gates — lives in Permission Reference. When in doubt, lean toward not granting. AC-6 Least Privilege is what auditors look for; "we gave everyone Risk Manager because it was easier" is not a defence.
Saving propagates to every user assigned to that role. save_role_responsibilities() updates the table, recomputes each affected user's effective permissions, and refreshes any active sessions via refresh_permissions_in_sessions_of_user() — a user whose role gained or lost a permission sees the change on their next page load without re-authenticating.
Assigning roles to users
A role only affects access once it's assigned. Assignment happens on the user side: the Role dropdown on Add Users for new users, the same dropdown on View Details for User for existing users. The tree stays editable, so you can grant the user one extra permission their role doesn't include without creating a new role for the difference. That per-user delta lives in permission_to_user.
When you delete a role with users assigned, SimpleRisk doesn't drop those users into a void. delete_role() reassigns each affected user to the current default role and recomputes their permissions as the union of (existing per-user permissions, minus the deleted role's responsibilities) plus (the default role's responsibilities). Active sessions refresh in place. If no default role is set, users land on role 0 with only their per-user permissions — usually nothing — so set a default role before you start deleting.
How the three layers compose
The cleanest way to understand the model is a worked example. Suppose Alice belongs to the Finance team and holds the Administrator role.
She can do everything. Administrator is admin (role.admin = 1), and the admin flag is the superuser bit: every permission check returns true, every team is implicitly hers, every page renders. The Finance membership is decorative — she'd see every team's risks regardless. This is why the Add User form locks the team multi-select when you grant Admin: there's no practical difference between "a team" and "all teams" for an admin user.
Now change Alice's role to a custom Risk Manager role you've created — one that grants the Risk Management, Submit Risks, Modify Risks, Plan Mitigations, and Close Risks permissions, with no admin flag. Each layer pulls its weight:
- User: Alice authenticates as
alice. The account exists, she's enabled, MFA's on. That gets her past the login. - Role: Risk Manager. The five permissions her role grants are bound onto her at session creation. Pages enforcing a permission she doesn't hold (the Configure menu, framework administration, the user-management pages) redirect or 403. Pages enforcing one she does (the risk register, mitigation planning) render normally.
- Team: Finance. Her membership writes a row to
user_to_team. With Team-Based Separation off (Core), the row only filters her dashboard saved-views; she still sees every risk her permissions allow. With the Separation Extra on, the same row gates her view: Alice sees only risks tagged to Finance, plus untagged risks. Other teams' risks are filtered out of every list, every report, every dashboard.
Each layer does one job. The user record is "who you are." The role is "what kinds of operations you can perform." The team is "which records those operations apply to" — but only when the Separation Extra promotes membership from a label into a control. Conflating the layers fights the model and produces brittle results.
Common pitfalls
-
Granting Risk Manager (or worse, Admin) to everyone "to keep it simple." This shows up in small teams that grew. Six months later the audit conversation asks who can approve a risk and the answer is "everyone" — not what auditors are looking for. Build the smallest role that does the job. AC-6 Least Privilege is testable; "we gave them all admin" is not.
-
Role sprawl. The opposite trap. Every department asks for a custom role; the product manager wants a Product role, the legal reviewer wants a Legal role, and a year later there are 40 roles nobody can keep straight. Build roles around what someone does, not what team they belong to. Risk Reviewer, Risk Submitter, Compliance Analyst — those scale. Marketing-Manager-Role-V2 doesn't.
-
Deleting users instead of disabling them. Deletion is permanent and severs attribution on every record. Disabling stops the login while preserving the audit trail. Disable as the default; reach for delete only when the account was created in error.
-
Treating teams as roles. "Put the security analysts in a Security team and grant the team some permissions." Teams don't carry permissions. Every attempt ends in confusion when the next admin tries to read the access model. If the analysts need different permissions, build a role.
-
Treating roles as teams. "Create a Finance role that scopes data to Finance's risks." Roles don't scope data. Every attempt ends in either over-broad access or wildly over-engineered roles the codebase doesn't support. If you need data segregation by team, the Team-Based Separation Extra is the right answer.
-
The single-admin org. One admin, no backup, no documented recovery plan. The admin leaves on a Friday, IT disables their IdP account on Monday, and nobody on the remaining team can get into Configure. Always have at least two admin accounts on a production instance, with at least one a service-style account whose credentials live in the org's privileged-access tooling rather than in one human's head.
Related
- The Permission Model
- Permission Reference
- Team-Based Segregation
- Organizational Hierarchy
- Local Authentication and Password Policies
- Multi-Factor Authentication
Reference
- Permission required:
admin(gates User Management, Team Management, and Role Management; the same permission theadmincolumn onuserand theadminflag onrolerepresent) - API endpoint(s):
GET /api/v2/admin/users/all(list all users),GET /api/v2/admin/users/enabled(enabled users),GET /api/v2/admin/users/disabled(disabled users),GET /api/v2/role_responsibilities/get_responsibilities(a role's permissions, used by the role-edit UI),GET /api/v2/user/manager(return the manager of a given user),POST /api/v2/reports/user_management_reports(the User Reports tab's datatable),GET /api/v2/reports/user_management_reports_unique_column_data(filter values for the same datatable). User, team, and role create / update / delete are form-only via the admin pages — no v2 CRUD endpoints exist for these resources today. - Implementing files:
simplerisk/admin/user_management.php(Add Users / Manage Users / User Settings / User Reports tabs and POST handlers)simplerisk/admin/view_user_details.php(the per-user edit page reached from Manage Users → Select)simplerisk/admin/team_management.php(team CRUD plus the per-team membership editor)simplerisk/admin/role_management.php(role CRUD plus the role-permission editor)simplerisk/includes/functions.php(helpers:add_user,update_user,enable_user,disable_user,delete_value,cleanup_after_delete,add_role,delete_role,save_role_responsibilities,update_team_membership_for_users,team_separation_extra,kill_sessions_of_user)simplerisk/includes/authenticate.php(thecheck_admingate that protects each admin page;kill_sessions_of_userfor terminating disabled or deleted users' sessions)simplerisk/includes/permissions.php(permission-checking helpers used by every gate referenced from a role's responsibilities)simplerisk/api/v2/index.php(the v2 routes listed above)simplerisk/api/v2/documentation/admin.php(OpenAPI annotations for the admin user-listing endpoints and the role-responsibilities endpoint) - Database tables:
user(the user record itself, includingadmin,enabled,lockout,last_login,manager,role_id, MFA fields, per-user display-settings JSON),team(value,name),role(id, name,adminflag,defaultflag),role_responsibilities(role-to-permission assignments, one row per pair),permissionsandpermission_groups(the catalogue of permission keys and their group display order),permission_to_user(per-user permission deltas added on top of the role),user_to_team(user-team membership),risk_to_team/mitigation_to_team/items_to_teams(team tags on risks, mitigations, and tests — relevant when Team-Based Separation gates visibility),user_pass_history(password history for the reuse policy),failed_login_attempts(lockout policy log),user_mfa(per-user MFA secrets),password_reset(in-flight admin-initiated password resets),audit_log(trail of admin actions on users, teams, and roles) config_settingskeys: Password policy:pass_policy_enabled,pass_policy_min_chars,pass_policy_alpha_required,pass_policy_upper_required,pass_policy_lower_required,pass_policy_digits_required,pass_policy_special_required,pass_policy_min_age,pass_policy_max_age,pass_policy_reuse_limit
Lockout policy:pass_policy_attempt_lockout,pass_policy_attempt_lockout_time
Authentication behavior:mfa_required,strict_user_validation,AUTHENTICATION_ADD_NEW_USERS(JIT provisioning toggle for SAML/LDAP; lives on the Authentication Extra)
Team segregation:team_separation(set by the Team-Based Separation Extra; gates whether team membership filters risk visibility)
Other:default_language(default UI language for new users)