Skip to content

Fix add_user_to_groups replacing a user's existing groups - #1490

Merged
MariusWirtz merged 1 commit into
cubewise-code:masterfrom
RobbyMeyers:fix/add-user-to-groups-appends
Oct 14, 2026
Merged

MariusWirtz merged 1 commit into
cubewise-code:masterfrom
RobbyMeyers:fix/add-user-to-groups-appends

Conversation

@RobbyMeyers

Copy link
Copy Markdown
Contributor

Problem

SecurityService.add_user_to_groups sends

PATCH /Users('<user>')
{"Name": "<user>", "Groups@odata.bind": ["Groups('<g>')", ...]}

containing only the groups passed in. On Planning Analytics 2.x / TM1 Server 11.8 (observed on 11.8.3700), this PATCH replaces the user's whole group collection rather than appending to it. The user silently loses every group that was not in the call.

Reproduced with TM1py 2.2.5 and 2.3.1; master has the same code.

Minimal repro

tm1.security.create_group("A")
tm1.security.create_group("B")
tm1.security.create_group("C")
tm1.security.create_user(User(name="TestUser", groups=["A", "B"], password="..."))

tm1.security.add_user_to_groups("TestUser", ["C"])

tm1.security.get_groups("TestUser")
# expected: ['A', 'B', 'C']
# actual:   ['C']

Fix

add_user_to_groups now reads the user's current groups and binds the union of current and requested groups in a single PATCH:

  • The result is the same whether the server treats Groups@odata.bind as append or replace. update_user already relies on sending the full group list.
  • Groups the user already belongs to are skipped (case- and space-insensitive via CaseAndSpaceInsensitiveSet), so re-adding a group doesn't duplicate it.
  • **kwargs are now forwarded to determine_actual_group_name. They weren't before.
  • Signature and return type (single Response) are unchanged.

Tests

Added to Tests/SecurityService_test.py:

  • test_add_user_to_groups_keeps_existing_groups: the user starts in Group1, Group2 is added, and both memberships are asserted.
  • test_add_user_to_groups_already_member: re-adding an existing group (different case) together with a new group leaves exactly one membership for the existing group and adds the new one.

ruff check and black --check pass on the changed files. The live-server tests weren't run against a TM1 instance as part of this PR. The new logic was checked offline against a mocked RestService: current [A, B] plus requested ["c", " b ", "C"] produces the bind list [A, B, C].

🤖 Generated with Claude Code

On recent TM1 versions (e.g. 11.8), PATCH /Users('x') with
Groups@odata.bind replaces the user's group collection instead of
appending to it, so add_user_to_groups dropped every group that was not
passed in.

Read the user's current groups and bind the union of current and new
groups. This behaves identically whether the server treats the bind as
append or replace, matching how update_user already sends the full list.
Groups the user already belongs to are skipped (case and space
insensitive), and kwargs are now forwarded to determine_actual_group_name.

Add tests asserting existing memberships survive and that re-adding an
existing group does not duplicate it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MariusWirtz
MariusWirtz merged commit 2888e39 into cubewise-code:master Oct 14, 2026
1 check 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.

2 participants