Skip to content

Added successor user, user role, and SAML-managed group options to users and groups - #1041

Merged
SteveMcGrath merged 5 commits into
tenable:mainfrom
lanz:RDC-25173-users-groups-options
Oct 13, 2026
Merged

SteveMcGrath merged 5 commits into
tenable:mainfrom
lanz:RDC-25173-users-groups-options

Conversation

@lanz

@lanz lanz commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Description

This adds three documented access-control options to tio.users and tio.groups. It also lets users.delete() take the user UUID, which is what the API reference documents for its path. Every new argument is keyword-only and defaults to None. When an argument is omitted, the request is exactly the same as before.

There's one commit per change, so each can be reviewed or reverted independently.

Commit Method Sent as
Updated users.delete to accept user UUIDs users.delete(user_id) now accepts the integer ID or the UUID path
Added successor user support to users.delete users.delete(..., successor_user_uuid=) successor_user_uuid query parameter
Added with_roles support to users.list users.list(with_roles=) withRoles query parameter, as lowercase true/false
Added managed_by_saml support to groups.create and groups.edit groups.create(..., managed_by_saml=), groups.edit(..., managed_by_saml=) managed_by_saml in the request body

Validation follows the modules' existing _check() style:

  • UUIDs for user_id and successor_user_uuid are lowercased, then validated before any request is sent. The API answers a malformed or unknown successor UUID with 404 Not Found, which reads like a missing user.
  • Booleans: with_roles and managed_by_saml must be booleans. The API silently ignores withRoles values other than true and false.

The docstrings describe what the API actually does, based on live testing:

  • Successor without enough permissions: returns 409 and leaves the user in place.
  • Web App Scanning scans aren't transferred, as the API reference notes.
  • groups.edit() without managed_by_saml keeps the current setting.
  • groups.edit() still requires name. The docstring says to pass the current name when changing only the flag.

No new dependencies. CHANGELOG.md and tenable/version.py are untouched.

Fixes #1040

Internal tracking: RDC-25173.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Unit tests. They use responses and add no cassettes. They assert the exact path, query string (query_param_matcher) or body (json_params_matcher with strict_match=True).

  • users.delete IDs: accepts an integer, a numeric string, and lowercase or uppercase UUIDs, and sends the expected path. A non-UUID string or a float raises before any request.
  • successor_user_uuid: sent lowercase. A malformed value raises UnexpectedValueError, and a non-string raises TypeError, both with no request sent. It can't be passed positionally.
  • with_roles: omitted, True and False produce no parameter, withRoles=true and withRoles=false. Invalid values raise TypeError with no request sent.
  • managed_by_saml: omitted, True and False produce the expected create and edit bodies. Invalid values raise TypeError with no request sent. It can't be passed positionally.
  • Fail on main: every test that sends a new value fails against main. Three groups pass there:
    • the integer-ID cases, which main already supported;
    • the omitted-option cases, which check that the request is unchanged;
    • the type-error cases, which raise TypeError on main only because it rejects the unknown keyword.
  • Per commit: each commit's lint and users and groups tests pass with only that commit checked out.
  • Lint and suite: ruff check tenable is clean, and ruff format reports no changes in the edited modules. The full suite passes on 3.11, 3.12, 3.13 and 3.14: 2201 passed, 0 failed, 95.6–95.8% coverage, and no new cassettes.
  • Bandit: bandit -ll results are unchanged from main (no medium or high issues).
  • Docs: the Sphinx build is unchanged from main (the same 6 warnings), and the new arguments and examples render on the users and groups pages.

Live API tests. These used the patched methods against three Tenable Vulnerability Management test environments. Throwaway users, scans and groups were created for the test and deleted afterward, with nothing left over. All 21 checks passed in each environment:

  • users.list(with_roles=True) added rbac_roles (name, uuid) to every user (19, 29 and 87 users). users.list() and with_roles=False returned the same users without it.
  • Successor transfer: a scan created by test user A was owned by user B after users.delete(A_uuid, successor_user_uuid=B_uuid). Both UUIDs were passed in uppercase.
  • users.delete('<numeric id>') deleted the user.
  • Successor without enough permissions raised a 409 APIError, and the user still existed. Invalid successors raised locally, and the user still existed.
  • groups.create(): managed_by_saml=True and False came back in the create response and the group list. Omitting it left the property absent.
  • groups.edit(): an edit without managed_by_saml kept True. False and True changed it as expected.

Test Configuration:

  • Python Version(s) Tested: 3.11.16, 3.12.14, 3.13.15, 3.14.7 (Windows 11)
  • Tenable.sc version (if necessary): N/A

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules (none required)

Notes for review

  • Omitting successor_user_uuid. In all three environments, the deleted user's scan went to the user making the request. Every requester was an administrator, though, so the API's actual rule is unconfirmed, and the docstring doesn't describe it.
  • users.delete(None) still sends DELETE users/None, as it did before this change, because _check() returns None for None input. I left that unchanged to keep this PR to the new options.

lanz added 4 commits October 3, 2026 19:50
The users-delete API documents the path parameter as the user UUID, and
the API accepts either the UUID or the integer ID. users.delete() only
accepted an integer. It now also accepts a UUID string, normalized to
lowercase and validated before the request is sent.
users.delete() now accepts a keyword-only successor_user_uuid, sent as
the users-delete query parameter of the same name, so a departing
user's objects can be transferred to another user. The UUID is
normalized to lowercase and validated first, because the API answers a
malformed UUID with 404 Not Found.

The docstring records behavior observed against the live API: a
successor without enough permissions returns 409 and the user is not
deleted.
users.list() now accepts a keyword-only with_roles boolean, sent as the
users-list withRoles query parameter in lowercase. When true, each user
record includes an rbac_roles list with the name and UUID of each
assigned role. The API silently ignores values other than true and false,
so other types raise TypeError before the request is sent.
…5173)

groups.create() and groups.edit() now accept a keyword-only
managed_by_saml boolean, sent in the groups-create and groups-edit
request bodies, to control whether SAML single sign-on manages group
membership. When omitted, the request body is unchanged.

Live testing showed that an edit without managed_by_saml keeps the
current setting, and that the API requires the name on every edit; the
docstrings say so.
@lanz
lanz requested a review from a team as a code owner October 4, 2026 01:57
@SteveMcGrath
SteveMcGrath merged commit 0c82b34 into tenable:main Oct 13, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Support successor users on user delete, roles on user list, and SAML-managed groups

2 participants