Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,34 @@ All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## 0.6.7 — 2026-10-30

A minor addition: group recursion and a user's profile/entity authorizations,
through the legacy v1 API. The GLPI v2 `Group` schema has no `is_recursive`
(a v2 create silently drops it) and v2 has no route for a user's
authorizations, so these helpers need `v1_base_url` / `v1_user_token` and
raise `RuntimeError` without them. Existing methods are unchanged.

### Added

- **`set_group_recursive(group_id, recursive=True)`** on both clients.
- **`list_user_authorizations`, `add_user_authorization`,
`update_user_authorization` and `delete_user_authorization`** on both
clients, over GLPI's `Profile_User` rows. `update_user_authorization` with
neither `entity_id` nor `recursive` raises `GlpiValidationError` without a
request.
- **`UserAuthorization`**, one authorization row (`id`, `user_id`,
`profile_id`, `entity_id`, `is_recursive`, `is_dynamic`,
`is_default_profile`), exported from the package root and from
`glpi_python_client.models`.

### Changed

- A v1 write that GLPI answers with `false` for an item (a success status and
a body such as `[{"7": false, "message": "..."}]`) raises
`GlpiProtocolError` with the server's message. The new helpers do this; the
existing v1 writes are unchanged.

## 0.6.6 — 2026-10-28

A minor addition: the GLPI v2 `/Administration/Group` routes, so a caller can
Expand Down
8 changes: 8 additions & 0 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,14 @@ Groups
:undoc-members:
:show-inheritance:

User authorizations
-------------------

.. autoclass:: UserAuthorization
:members:
:undoc-members:
:show-inheritance:

Plugin: Fields (custom fields)
------------------------------

Expand Down
35 changes: 35 additions & 0 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ The full set is re-exported from the package root, including
``GetLocation`` / ``PostLocation`` / ``PatchLocation`` / ``DeleteLocation``,
``GetEntity`` / ``PostEntity`` / ``PatchEntity`` / ``DeleteEntity``,
``GetGroup`` / ``PostGroup`` / ``PatchGroup`` / ``DeleteGroup``,
``UserAuthorization``,
``GetFollowup``, ``GetTicketTask``, ``GetSolution``, ``GetTimelineDocument``,
``GetTeamMember``, and ``GetDocument`` together with their post / patch /
delete variants. The read-only ``GetDocumentItem`` and ``FollowupDocuments``
Expand Down Expand Up @@ -582,6 +583,40 @@ Example output::
the client (``v1_base_url`` and ``v1_user_token``) because the GLPI v2
contract does not advertise a binary upload endpoint.

Group recursion and user authorizations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

GLPI lets a group, and a user's profile in an entity, apply to the
sub-entities of that entity too. The v2 API cannot set either: its ``Group``
schema has no ``is_recursive`` property (a v2 create silently drops the key)
and it has no route at all for a user's profile and entity authorizations.
Five helpers go through the legacy v1 session instead (``v1_base_url`` and
``v1_user_token``); without one they raise ``RuntimeError``.

* ``set_group_recursive(group_id, recursive=True)`` sets the flag on one
group.
* ``list_user_authorizations(user_id)`` returns the user's profiles as
:class:`~glpi_python_client.UserAuthorization` rows: the row ``id``, the
``profile_id`` and ``entity_id``, and the ``is_recursive``, ``is_dynamic``
and ``is_default_profile`` flags.
* ``add_user_authorization(user_id, profile_id=..., entity_id=...,
recursive=False)`` grants a profile in an entity and returns the new row id.
* ``update_user_authorization(authorization_id, entity_id=None,
recursive=None)`` moves a row to another entity or changes its recursion;
with neither argument it raises ``GlpiValidationError`` without a request.
* ``delete_user_authorization(authorization_id)`` removes a row.

The v1 API answers a refused write with a success status and a body such as
``[{"7": false, "message": "..."}]``. These helpers raise
``GlpiProtocolError`` with that message instead of returning as if it had
worked.

.. code-block:: python

await client.set_group_recursive(119)
rows = await client.list_user_authorizations(2568)
await client.update_user_authorization(rows[0].id, entity_id=493, recursive=True)

Documents linked to a followup
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
4 changes: 3 additions & 1 deletion glpi_python_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,15 @@
PostTimelineDocument,
PostUser,
TicketMarkdownOptions,
UserAuthorization,
)
from glpi_python_client.rsql import (
changed_since,
created_between,
date_window,
)

__version__ = "0.6.6"
__version__ = "0.6.7"

__all__ = [
"AsyncGlpiClient",
Expand Down Expand Up @@ -250,6 +251,7 @@
"PostTimelineDocument",
"PostUser",
"TicketMarkdownOptions",
"UserAuthorization",
"__version__",
"changed_since",
"created_between",
Expand Down
43 changes: 43 additions & 0 deletions glpi_python_client/_async/clients/api/administration/_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
from collections.abc import AsyncIterator

from glpi_python_client._async.clients.commons._constants import GROUP_ENDPOINT, GlpiId
from glpi_python_client._async.clients.commons._payloads import (
ensure_v1_write_accepted,
)
from glpi_python_client._async.clients.commons._transport import TransportMixin
from glpi_python_client.models.api_schema.administration._group import (
DeleteGroup,
Expand All @@ -19,6 +22,8 @@
PostGroup,
)

_V1_GROUP_FEATURE_LABEL = "group recursion"


class GroupMixin(TransportMixin):
"""CRUD helpers for ``/Administration/Group``."""
Expand Down Expand Up @@ -214,5 +219,43 @@ async def delete_group(
delete_model_cls=DeleteGroup,
)

async def set_group_recursive(
self, group_id: GlpiId, recursive: bool = True
) -> None:
"""Set whether one group is visible in the sub-entities of its entity.

The GLPI v2 ``Group`` schema has no ``is_recursive`` property (a v2
create or update silently drops the key), so this helper writes the
flag through the legacy v1 API, which requires
``v1_base_url``/``v1_user_token`` to be configured (pointing at the
legacy ``apirest.php``).

Parameters
----------
group_id : int | str
Identifier of the group.
recursive : bool, optional
``True`` (the default) makes the group apply to the sub-entities
of its entity; ``False`` restricts it to its own entity.

Raises
------
RuntimeError
When no legacy v1 session is configured on the client.
GlpiStatusError
When the legacy API returns a non-success status.
GlpiProtocolError
When the legacy API answers that the update was refused.
"""

v1 = self._require_v1_session(_V1_GROUP_FEATURE_LABEL)
payload = await v1.request_json(
"PUT",
f"Group/{group_id}",
json_body={"input": {"id": int(group_id), "is_recursive": int(recursive)}},
failure_message=f"Failed to set is_recursive on group {group_id}",
)
ensure_v1_write_accepted(payload, f"Setting is_recursive on group {group_id}")


__all__ = ["GroupMixin"]
Loading
Loading