Problem: Group Listing Fails Due to Incorrect isAssignableToRole Type

Problem

You want to print Users and Groups for a Azure AD alias connection and running /usr/share/univention-office365/scripts/print_users_and_groups Uni-connection the following error occurs:

Using Azure AD connection alias 'Uni-connection'.
Traceback (most recent call last):
  File "/usr/share/univention-office365/scripts/print_users_and_groups", line 150, in <module>
    print_users_and_groups(adconnection_alias)
  File "/usr/share/univention-office365/scripts/print_users_and_groups", line 42, in print_users_and_groups
    groups = GroupAzure.list(core)
             ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/univention/office365/microsoft/objects/azureobjects.py", line 999, in list
    groups = [GroupAzure.from_dict(x) for x in groups_value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/univention/office365/microsoft/objects/azureobjects.py", line 999, in <listcomp>
    groups = [GroupAzure.from_dict(x) for x in groups_value]
              ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/univention/office365/microsoft/objects/azureobjects.py", line 121, in from_dict
    return cls(**{a.name: data.get(a.name, a.default) for a in cls.__attrs_attrs__})
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<attrs generated init univention.office365.microsoft.objects.azureobjects.GroupAzure>", line 110, in __init__
  File "/usr/lib/python3/dist-packages/attr/validators.py", line 105, in __call__
    raise TypeError(
TypeError: ("'isAssignableToRole' must be (<class 'str'>, <class 'NoneType'>) (got False that is a <class 'bool'>).", Attribute(name='isAssignableToRole', default=None, validator=<instance_of validator for type (<class 'str'>, <class 'NoneType'>)>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='isAssignableToRole'), (<class 'str'>, <class 'NoneType'>), False)

Root Cause

Microsoft Azure returns the isAssignableToRole attribute as a Boolean value (true or false). The office365 Connector incorrectly defines this attribute as a string.
When the Azure response is parsed, the Boolean value is passed directly to GroupAzure.from_dict(). The attrs validator expects a string or None and raises a
TypeError when it receives the Boolean value false.

Microsoft documents isAssignableToRole as a Boolean attribute:

This will be also tracked in Bug 59902


Investigation

The affected file is:
modules/univention/office365/microsoft/objects/azureobjects.py

The problematic code is:

isAssignableToRole = attr.ib(
    validator=attr.validators.instance_of((str, type(None))),
    default=None,
)  #: str
GroupAzure.from_dict() passes response values directly to the object:
return cls(**{
    a.name: data.get(a.name, a.default)
    for a in cls.__attrs_attrs__
})

Therefore, a response value of false is validated as a Python bool against the expected types str and None, causing the traceback.


Workaround

A patch file is provided as fix-isAssignableToRole.patch on the Bugreport.
The patch changes the validator to accept Boolean values:

From:
- isAssignableToRole = attr.ib(validator=attr.validators.instance_of((str, type(None))), default=None) #: str

To:
+ isAssignableToRole = attr.ib(validator=attr.validators.instance_of((bool, type(None))), default=None) #: bool

To apply the patch on UCS 5.2

1. Install the patch software:

apt update && apt install patch

2. Create a backup of the installed Python module:

cp \
  /usr/lib/python3/dist-packages/univention/office365/microsoft/objects/azureobjects.py \
  /usr/lib/python3/dist-packages/univention/office365/microsoft/objects/azureobjects.py.bak

3. Create the patchfile fix-isAssignableToRole.patch

cat > fix-isAssignableToRole.patch <<'EOF'
diff --git a/modules/univention/office365/microsoft/objects/azureobjects.py b/modules/univention/office365/microsoft/objects/azureobjects.py
--- a/modules/univention/office365/microsoft/objects/azureobjects.py
+++ b/modules/univention/office365/microsoft/objects/azureobjects.py
@@ -822,7 +822,7 @@ class GroupAzure(AzureObject):
     deletedDateTime = attr.ib(validator=attr.validators.instance_of((str, type(None))), default=None)  #: str
     creationOptions = attr.ib(validator=attr.validators.instance_of((List, type(None))), default=None)  #: List
     expirationDateTime = attr.ib(validator=attr.validators.instance_of((str, type(None))), default=None)  #: str
-    isAssignableToRole = attr.ib(validator=attr.validators.instance_of((str, type(None))), default=None)  #: str
+    isAssignableToRole = attr.ib(validator=attr.validators.instance_of((bool, type(None))), default=None)  #: bool
     membershipRule = attr.ib(validator=attr.validators.instance_of((str, type(None))), default=None)  #: str
     membershipRuleProcessingState = attr.ib(validator=attr.validators.instance_of((str, type(None))), default=None)  #: str
     onPremisesDomainName = attr.ib(validator=attr.validators.instance_of((str, type(None))), default=None)  #: str
EOF

4. Apply the patch to the installed module:

patch \
  /usr/lib/python3/dist-packages/univention/office365/microsoft/objects/azureobjects.py \
  < fix-isAssignableToRole.patch

5. Confirm that the change is present:

grep -n "isAssignableToRole" \
  /usr/lib/python3/dist-packages/univention/office365/microsoft/objects/azureobjects.py

The line must contain:
825: isAssignableToRole = attr.ib(validator=attr.validators.instance_of((bool, type(None))), default=None) #: bool

6. Run the command again:

/usr/share/univention-office365/scripts/print_users_and_groups Uni-connection

Hint:
This workaround changes an installed package file. The change may be overwritten by a future package update.