diff --git a/grouper_python/__init__.py b/grouper_python/__init__.py index 12b2e23..da3ae0e 100644 --- a/grouper_python/__init__.py +++ b/grouper_python/__init__.py @@ -4,5 +4,5 @@ Client = GrouperClient -__version__ = "0.1.3" +__version__ = "0.1.4" __all__ = ["GrouperClient"] diff --git a/grouper_python/attribute.py b/grouper_python/attribute.py new file mode 100644 index 0000000..597f4ad --- /dev/null +++ b/grouper_python/attribute.py @@ -0,0 +1,559 @@ +"""grouper-python.attribute - functions to interact with grouper attributess. + +These are "helper" functions that normally will not be called directly. +However these are still in development, +and are not usable from any objects currently. +They can be used by calling directly. +""" + +from __future__ import annotations +from typing import TYPE_CHECKING, Any, overload, Literal + +if TYPE_CHECKING: # pragma: no cover + from .objects.client import GrouperClient + from .objects.subject import Subject + from .objects.attribute import ( + AttributeDefinition, + AttributeDefinitionName, + AttributeAssignment, + ) + + +@overload +def assign_attribute( + attribute_assign_type: str, + assign_operation: str, + client: GrouperClient, + attribute_assign_id: str | None = None, + owner_name: str | None = None, + attribute_def_name_name: str | None = None, + value: None | str | int | float = None, + assign_value_operation: str | None = None, + *, + raw: Literal[False] = False, + act_as_subject: Subject | None = None, +) -> list[AttributeAssignment]: # pragma: no cover + ... + + +@overload +def assign_attribute( + attribute_assign_type: str, + assign_operation: str, + client: GrouperClient, + attribute_assign_id: str | None = None, + owner_name: str | None = None, + attribute_def_name_name: str | None = None, + value: None | str | int | float = None, + assign_value_operation: str | None = None, + *, + raw: Literal[True], + act_as_subject: Subject | None = None, +) -> dict[str, Any]: # pragma: no cover + ... + + +def assign_attribute( + attribute_assign_type: str, + assign_operation: str, + client: GrouperClient, + attribute_assign_id: str | None = None, + owner_name: str | None = None, + attribute_def_name_name: str | None = None, + value: None | str | int | float = None, + assign_value_operation: str | None = None, + *, + raw: bool = False, + act_as_subject: Subject | None = None, +) -> list[AttributeAssignment] | dict[str, Any]: + """Assign an attribute. + + :param attribute_assign_type: Type of attribute assignment, + currently only "group" is supported + :type attribute_assign_type: str + :param assign_operation: Assignment operation, one of + "assign_attr", "add_attr", "remove_attr", "replace_attrs" + :type assign_operation: str + :param client: The GrouperClient to use + :type client: GrouperClient + :param attribute_assign_id: Existing assignment id, if modifying + an existing assignment, defaults to None + :type attribute_assign_id: str | None, optional + :param owner_name: Name of owner to assign attribute to, + defaults to None + :type owner_name: str | None, optional + :param attribute_def_name_name: Attribute definition name name to assign, + defaults to None + :type attribute_def_name_name: str | None, optional + :param value: Value to assign, also requires assign_value_operation, + defaults to None + :type value: None | str | int | float, optional + :param assign_value_operation: Value assignment operation, one of + "assign_value", "add_value", "remove_value", "replace_values", + requires value to be specified as well, defaults to None + :type assign_value_operation: str | None, optional + :param raw: Whether to return a raw dictionary of results instead + of Python objects, defaults to False + :type raw: bool, optional + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + :raises ValueError: An unknown or unsupported attribute_assign_type is given + :return: A list of modified AttributeAssignments or the raw dictionary result + from Grouper, depending on the value of raw + :rtype: list[AttributeAssignment] | dict[str, Any] + """ + from .objects.attribute import ( + AttributeDefinition, + AttributeDefinitionName, + AttributeAssignment, + ) + from .objects.group import Group + from .objects.stem import Stem + + request: dict[str, Any] = { + "attributeAssignType": attribute_assign_type, + "attributeAssignOperation": assign_operation, + } + if attribute_assign_type == "group": + if owner_name: + request["wsOwnerGroupLookups"] = [{"groupName": owner_name}] + else: + request["wsOwnerGroupLookups"] = [] + elif attribute_assign_type == "stem": + if owner_name: + request["wsOwnerStemLookups"] = [{"stemName": owner_name}] + else: + request["wsOwnerStemLookups"] = [] + elif attribute_assign_type == "member": # pragma: no cover + if owner_name: + request["wsOwnerSubjectLookups"] = [{"identifier": owner_name}] + else: + request["wsOwnerSubjectLookups"] = [] + elif attribute_assign_type == "attr_def": # pragma: no cover + if owner_name: + request["wsOwnerAttributeLookups"] = [{"name": owner_name}] + else: + request["wsOwnerAttributeLookups"] = [] + else: # pragma: no cover + raise ValueError("Unknown or unsupported attributeAssignType given") + if attribute_assign_id: + request["wsAttributeAssignLookups"] = [{"uuid": attribute_assign_id}] + if attribute_def_name_name: + request["wsAttributeDefNameLookups"] = [{"name": attribute_def_name_name}] + + if value and assign_value_operation: + request["values"] = [{"valueSystem": value}] + request["attributeAssignValueOperation"] = assign_value_operation + + body = {"WsRestAssignAttributesRequest": request} + + r = client._call_grouper( + "/attributeAssignments", body, act_as_subject=act_as_subject + ) + if raw: # pragma: no cover + return r + + results = r["WsAssignAttributesResults"] + + ws_attribute_defs = results["wsAttributeDefs"] + ws_attribute_def_names = results["wsAttributeDefNames"] + _attribute_defs = { + ws_attr_def["uuid"]: AttributeDefinition(client, ws_attr_def) + for ws_attr_def in ws_attribute_defs + } + _attribute_def_names = { + ws_attr_def_name["uuid"]: AttributeDefinitionName( + client, + ws_attr_def_name, + _attribute_defs[ws_attr_def_name["attributeDefId"]], + ) + for ws_attr_def_name in ws_attribute_def_names + } + + r_list: list[AttributeAssignment] = [] + + if attribute_assign_type == "group": + groups = {group["uuid"]: Group(client, group) for group in results["wsGroups"]} + for assign_result in results["wsAttributeAssignResults"]: + for assg in assign_result["wsAttributeAssigns"]: + r_list.append( + AttributeAssignment( + client, + assg, + _attribute_defs[assg["attributeDefId"]], + _attribute_def_names[assg["attributeDefNameId"]], + group=groups[assg["ownerGroupId"]], + ) + ) + elif attribute_assign_type == "stem": + stems = {stem["uuid"]: Stem(client, stem) for stem in results["wsStems"]} + for assign_result in results["wsAttributeAssignResults"]: + for assg in assign_result["wsAttributeAssigns"]: + r_list.append( + AttributeAssignment( + client, + assg, + _attribute_defs[assg["attributeDefId"]], + _attribute_def_names[assg["attributeDefNameId"]], + stem=stems[assg["ownerStemId"]], + ) + ) + else: # pragma: no cover + raise ValueError("Unknown or unsupported attributeAssignType given, use raw") + + return r_list + + +@overload +def get_attribute_assignments( + attribute_assign_type: str, + client: GrouperClient, + attribute_def_name_names: list[str] = [], + attribute_def_names: list[str] = [], + owner_names: list[str] = [], + include_assignments_on_assignments: str = "F", + *, + raw: Literal[False] = False, + act_as_subject: Subject | None = None, +) -> list[AttributeAssignment]: # pragma: no cover + ... + + +@overload +def get_attribute_assignments( + attribute_assign_type: str, + client: GrouperClient, + attribute_def_name_names: list[str] = [], + attribute_def_names: list[str] = [], + owner_names: list[str] = [], + include_assignments_on_assignments: str = "F", + *, + raw: Literal[True], + act_as_subject: Subject | None = None, +) -> dict[str, Any]: # pragma: no cover + ... + + +def get_attribute_assignments( + attribute_assign_type: str, + client: GrouperClient, + attribute_def_name_names: list[str] = [], + attribute_def_names: list[str] = [], + owner_names: list[str] = [], + include_assignments_on_assignments: str = "F", + *, + raw: bool = False, + act_as_subject: Subject | None = None, +) -> list[AttributeAssignment] | dict[str, Any]: + """Get attribute assignments. + + :param attribute_assign_type: Type of attribute assignment, + must be a direct assignment type, one of + "group", "member", "stem", "any_mem", "imm_mem", "attr_def" + :type attribute_assign_type: str + :param client: The GrouperClient to use + :type client: GrouperClient + :param attribute_def_name_names: List of names of attribute definition names + to retrieve assignments for, defaults to [] + :type attribute_def_name_names: list[str], optional + :param attribute_def_names: List of names of attribute defitions + to retrieve assignments for, defaults to [] + :type attribute_def_names: list[str], optional + :param owner_names: List of owners to retrieve assignments for, + if specified, only group, member, stem, or attr_def + are allowed for attribute_assign_type, defaults to [] + :type owner_names: list[str], optional + :param include_assignments_on_assignments: Specify "T" to get + assignments on assignments, defaults to "F" + :type include_assignments_on_assignments: str, optional + :param raw: Whether to return a raw dictionary of results instead + of Python objects, defaults to False + :type raw: bool, optional + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + :raises ValueError: An unknown or unsupported attribute_assign_type is given + :raises ValueError: The given attribute_assign_type is not supported as a Python + object, specify raw instead + :return:a list of AttributeAssignments or the raw dictionary result + from Grouper, depending on the value of raw + :rtype: list[AttributeAssignment] | dict[str, Any] + """ + from .objects.attribute import ( + AttributeDefinition, + AttributeDefinitionName, + AttributeAssignment, + ) + from .objects.group import Group + from .objects.stem import Stem + + request: dict[str, Any] = { + "attributeAssignType": attribute_assign_type, + "includeAssignmentsOnAssignments": include_assignments_on_assignments, + } + + if attribute_assign_type == "group": + request["wsOwnerGroupLookups"] = [{"groupName": name} for name in owner_names] + elif attribute_assign_type == "stem": + request["wsOwnerStemLookups"] = [{"stemName": name} for name in owner_names] + elif attribute_assign_type == "member": # pragma: no cover + request["wsOwnerSubjectLookups"] = [ + {"identifier": name} for name in owner_names + ] + elif attribute_assign_type == "attr_def": # pragma: no cover + request["wsOwnerAttributeLookups"] = [{"name": name} for name in owner_names] + else: # pragma: no cover + raise ValueError("Unknown or unsupported attributeAssignType given") + + request["wsAttributeDefNameLookups"] = [ + {"name": name} for name in attribute_def_name_names + ] + request["wsAttributeDefLookups"] = [{"name": name} for name in attribute_def_names] + + body = {"WsRestGetAttributeAssignmentsRequest": request} + + r = client._call_grouper( + "/attributeAssignments", body, act_as_subject=act_as_subject + ) + if raw: # pragma: no cover + return r + + results = r["WsGetAttributeAssignmentsResults"] + + if "wsAttributeAssigns" not in results: + return [] + + ws_attribute_defs = results["wsAttributeDefs"] + ws_attribute_def_names = results["wsAttributeDefNames"] + _attribute_defs = { + ws_attr_def["uuid"]: AttributeDefinition(client, ws_attr_def) + for ws_attr_def in ws_attribute_defs + } + _attribute_def_names = { + ws_attr_def_name["uuid"]: AttributeDefinitionName( + client, + ws_attr_def_name, + _attribute_defs[ws_attr_def_name["attributeDefId"]], + ) + for ws_attr_def_name in ws_attribute_def_names + } + + if attribute_assign_type == "group": + groups = {group["uuid"]: Group(client, group) for group in results["wsGroups"]} + return [ + AttributeAssignment( + client, + assg, + _attribute_defs[assg["attributeDefId"]], + _attribute_def_names[assg["attributeDefNameId"]], + group=groups[assg["ownerGroupId"]], + ) + for assg in results["wsAttributeAssigns"] + ] + elif attribute_assign_type == "stem": + stems = {stem["uuid"]: Stem(client, stem) for stem in results["wsStems"]} + return [ + AttributeAssignment( + client, + assg, + _attribute_defs[assg["attributeDefId"]], + _attribute_def_names[assg["attributeDefNameId"]], + stem=stems[assg["ownerStemId"]], + ) + for assg in results["wsAttributeAssigns"] + ] + else: # pragma: no cover + raise ValueError("Unknown or unsupported attributeAssignType given, use raw") + + +@overload +def get_attribute_definitions( + attribute_def_name: str, + client: GrouperClient, + scope: str | None = None, + split_scope: str = "F", + parent_stem_id: str | None = None, + stem_scope: str | None = None, + *, + raw: Literal[False] = False, + act_as_subject: Subject | None = None, +) -> list[AttributeDefinition]: # pragma: no cover + ... + + +@overload +def get_attribute_definitions( + attribute_def_name: str, + client: GrouperClient, + scope: str | None = None, + split_scope: str = "F", + parent_stem_id: str | None = None, + stem_scope: str | None = None, + *, + raw: Literal[True], + act_as_subject: Subject | None = None, +) -> dict[str, Any]: # pragma: no cover + ... + + +def get_attribute_definitions( + attribute_def_name: str, + client: GrouperClient, + scope: str | None = None, + split_scope: str = "F", + parent_stem_id: str | None = None, + stem_scope: str | None = None, + *, + raw: bool = False, + act_as_subject: Subject | None = None, +) -> list[AttributeDefinition] | dict[str, Any]: + """Get attribute definitions. + + Note: it appears that if the given attribute_def_name cannot + be found exactly, the endpoint will return all definitions that + match the remaining criteria. + + :param attribute_def_name: Name of attribute definition to get + :type attribute_def_name: str + :param client: The GrouperClient to use + :type client: GrouperClient + :param scope: Search string with % as wildcards will search name, + display name, description, defaults to None + :type scope: str | None, optional + :param split_scope: "T" or "F", if T will split the scope by whitespace, + and find attribute defs with each token, defaults to "F" + :type split_scope: str, optional + :param parent_stem_id: ID of parent stem to limit search for, + if specified stem_scope is also required, defaults to None + :type parent_stem_id: str | None, optional + :param stem_scope: Scope in stem to search for, + if specified must be one of "ONE_LEVEL" or "ALL_IN_SUBTREE", + and parent_stem_id must also be specified, defaults to None + :type stem_scope: str | None, optional + :param raw: Whether to return a raw dictionary of results instead + of Python objects, defaults to False + :type raw: bool, optional + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + :return: a list of AttributeDefinitions or the raw dictionary result + from Grouper, depending on the value of raw + :rtype: list[AttributeDefinition] | dict[str, Any] + """ + from .objects.attribute import AttributeDefinition + + body = { + "WsRestFindAttributeDefsLiteRequest": { + "nameOfAttributeDef": attribute_def_name, + } + } + if scope: + body["WsRestFindAttributeDefsLiteRequest"]["scope"] = scope + body["WsRestFindAttributeDefsLiteRequest"]["splitScope"] = split_scope + + if parent_stem_id and stem_scope: + body["WsRestFindAttributeDefsLiteRequest"]["parentStemId"] = parent_stem_id + body["WsRestFindAttributeDefsLiteRequest"]["stemScope"] = stem_scope + + r = client._call_grouper("/attributeDefs", body, act_as_subject=act_as_subject) + if raw: + return r + results = r["WsFindAttributeDefsResults"] + + if "attributeDefResults" in results: + return [ + AttributeDefinition(client, attribute_def_body) + for attribute_def_body in results["attributeDefResults"] + ] + else: + return [] + + +@overload +def get_attribute_definition_names( + client: GrouperClient, + attribute_def_name_name: str | None = None, + name_of_attribute_def: str | None = None, + scope: str | None = None, + *, + raw: Literal[False] = False, + act_as_subject: Subject | None = None, +) -> list[AttributeDefinitionName]: # pragma: no cover + ... + + +@overload +def get_attribute_definition_names( + client: GrouperClient, + attribute_def_name_name: str | None = None, + name_of_attribute_def: str | None = None, + scope: str | None = None, + *, + raw: Literal[True], + act_as_subject: Subject | None = None, +) -> dict[str, Any]: # pragma: no cover + ... + + +def get_attribute_definition_names( + client: GrouperClient, + attribute_def_name_name: str | None = None, + name_of_attribute_def: str | None = None, + scope: str | None = None, + *, + raw: bool = False, + act_as_subject: Subject | None = None, +) -> list[AttributeDefinitionName] | dict[str, Any]: + """Get Attribute Definition Names. + + :param client: The GrouperClient to use + :type client: GrouperClient + :param attribute_def_name_name: The name of the attribute definition name + to retrieve, defaults to None + :type attribute_def_name_name: str | None, optional + :param name_of_attribute_def: The name of the attribute definition to + retrieve attribute definition names for, defaults to None + :type name_of_attribute_def: str | None, optional + :param scope: Search string with % as wildcards, + will search name, display name, description, defaults to None + :type scope: str | None, optional + :param raw: Whether to return a raw dictionary of results instead + of Python objects, defaults to False + :type raw: bool, optional + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + :return: a list of AttributeDefinitionNames or the raw dictionary result + from Grouper, depending on the value of raw + :rtype: list[AttributeDefinitionName] | dict[str, Any] + """ + from .objects.attribute import AttributeDefinitionName, AttributeDefinition + + request: dict[str, str] = {} + if attribute_def_name_name: + request["attributeDefNameName"] = attribute_def_name_name + if name_of_attribute_def: + request["nameOfAttributeDef"] = name_of_attribute_def + if scope: + request["scope"] = scope + body = {"WsRestFindAttributeDefNamesLiteRequest": request} + + r = client._call_grouper("/attributeDefNames", body, act_as_subject=act_as_subject) + if raw: + return r + results = r["WsFindAttributeDefNamesResults"] + + if "attributeDefNameResults" not in results: + return [] + + ws_attribute_defs = results.get("attributeDefs", []) + ws_attribute_def_names = results["attributeDefNameResults"] + + attribute_defs = { + ws_attr_def["uuid"]: AttributeDefinition(client, ws_attr_def) + for ws_attr_def in ws_attribute_defs + } + + return [ + AttributeDefinitionName( + client, attr_name, attribute_defs[attr_name["attributeDefId"]] + ) + for attr_name in ws_attribute_def_names + ] diff --git a/grouper_python/objects/__init__.py b/grouper_python/objects/__init__.py index 3c93669..db5dbc2 100644 --- a/grouper_python/objects/__init__.py +++ b/grouper_python/objects/__init__.py @@ -1,9 +1,31 @@ """grouper_python.objects, Classes for the grouper_python package.""" -from .group import Group +from .group import Group, CreateGroup from .person import Person -from .stem import Stem +from .stem import Stem, CreateStem from .subject import Subject from .privilege import Privilege +from .membership import Membership, MemberType, MembershipType +from .attribute import ( + AttributeDefinition, + AttributeDefinitionName, + AttributeAssignment, + AttributeAssignmentValue +) -__all__ = ["Group", "Person", "Stem", "Subject", "Privilege"] +__all__ = [ + "Group", + "Person", + "Stem", + "Subject", + "Privilege", + "CreateGroup", + "CreateStem", + "Membership", + "MemberType", + "MembershipType", + "AttributeDefinition", + "AttributeDefinitionName", + "AttributeAssignment", + "AttributeAssignmentValue", +] diff --git a/grouper_python/objects/attribute.py b/grouper_python/objects/attribute.py new file mode 100644 index 0000000..a7c4c13 --- /dev/null +++ b/grouper_python/objects/attribute.py @@ -0,0 +1,192 @@ +"""grouper_python.objects.attribute - Objects related to Grouper attributes.""" + +from __future__ import annotations +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: # pragma: no cover + from .group import Group + from .stem import Stem + from .client import GrouperClient + from .subject import Subject +from dataclasses import dataclass +from .base import GrouperEntity, GrouperBase +from ..attribute import assign_attribute + + +@dataclass(slots=True, eq=False) +class AttributeDefinition(GrouperEntity): + """AttributeDefinition object representing a Grouper attribute definition. + + :param client: A GrouperClient object containing connection information + :type client: GrouperClient + :param attribute_def_body: Body of the attribute definition + as returned by the Grouper API + :type attribute_def_body: dict[str, str] + """ + + extension: str + attributeDefType: str + valueType: str + assignToAttributeDef: str + assignToStem: str + assignToGroup: str + assignToMember: str + assignToEffectiveMembership: str + assignToImmediateMembership: str + assignToAttributeDefAssignment: str + assignToStemAssignment: str + assignToGroupAssignment: str + assignToMemberAssignment: str + assignToEffectiveMembershipAssignment: str + assignToImmediateMembershipAssignment: str + multiAssignable: str + multiValued: str + idIndex: str + + def __init__( + self, + client: GrouperClient, + attribute_def_body: dict[str, str] + ) -> None: + """Construct an AttributeDefinition.""" + self.client = client + self.description = attribute_def_body.get("description", "") + self.id = attribute_def_body["uuid"] + for key, value in attribute_def_body.items(): + if key not in ["description", "uuid"]: + setattr(self, key, value) + + +@dataclass(slots=True, eq=False) +class AttributeDefinitionName(GrouperEntity): + """AttributeDefinitionName object representing a Grouper attribute definition name. + + :param client: A GrouperClient object containing connection information + :type client: GrouperClient + :param attribute_def_name_body: Body of the attribute definition name + as returned by the Grouper API + :type attribute_def_name_body: dict[str, str] + :param attribute_def: AttributeDefinition object associated with this + attribute definition name + :type attribute_def: AttributeDefinition + """ + + displayExtension: str + extension: str + displayName: str + idIndex: str + attribute_definition: AttributeDefinition + + def __init__( + self, + client: GrouperClient, + attribute_def_name_body: dict[str, str], + attribute_def: AttributeDefinition + ) -> None: + """Construct an AttributeDefinitionName.""" + self.client = client + self.displayExtension = attribute_def_name_body["displayExtension"] + self.extension = attribute_def_name_body["extension"] + self.displayName = attribute_def_name_body["displayName"] + self.idIndex = attribute_def_name_body["idIndex"] + self.name = attribute_def_name_body["name"] + self.description = attribute_def_name_body.get("description", "") + self.id = attribute_def_name_body["uuid"] + self.attribute_definition = attribute_def + + +@dataclass +class AttributeAssignmentValue: + """Class representing an attribute assignment value.""" + + id: str + valueSystem: str + + +@dataclass(slots=True, eq=False) +class AttributeAssignment(GrouperBase): + """AttributeAssignemtn object representing a Grouper attribute assignment. + + :param client: A GrouperClient object containing connection information + :type client: GrouperClient + :param assign_body: Body of the assignment as returned by the Grouper API + :type assign_body: dict[str, Any] + :param attribute_def: AttributeDefinition object associated with this assignment + :type attribute_def: AttributeDefinition + :param attribute_def_name: AttributeDefinitionName object + associated with this assignment + :type attribute_def_name: AttributeDefinitionName + :param group: owner group of this assignment, defaults to None + :type group: Group | None, optional + :param stem: owner stem of this assignment, defaults to None + :type stem: Stem | None, optional + """ + + attributeAssignDelegatable: str + disallowed: str + createdOn: str + enabled: str + attributeAssignType: str + lastUpdated: str + attributeAssignActionId: str + attributeAssignActionName: str + attributeAssignActionType: str + id: str + owner: Group | Stem + group: Group | None + stem: Stem | None + values: list[AttributeAssignmentValue] + attribute_definition: AttributeDefinition + attribute_definition_name: AttributeDefinitionName + + def __init__( + self, + client: GrouperClient, + assign_body: dict[str, Any], + attribute_def: AttributeDefinition, + attribute_def_name: AttributeDefinitionName, + *, + group: Group | None = None, + stem: Stem | None = None + ) -> None: + """Construct an AttributeAssignment.""" + self.client = client + self.attributeAssignDelegatable = assign_body["attributeAssignDelegatable"] + self.disallowed = assign_body["disallowed"] + self.createdOn = assign_body["createdOn"] + self.enabled = assign_body["enabled"] + self.attributeAssignType = assign_body["attributeAssignType"] + self.lastUpdated = assign_body["lastUpdated"] + self.attributeAssignActionId = assign_body["attributeAssignActionId"] + self.attributeAssignActionName = assign_body["attributeAssignActionName"] + self.attributeAssignActionType = assign_body["attributeAssignActionType"] + self.id = assign_body["id"] + self.group = group + self.stem = stem + if group: + self.owner = group + if stem: + self.owner = stem + self.values = [ + AttributeAssignmentValue(val["id"], val["valueSystem"]) + for val in assign_body["wsAttributeAssignValues"] + ] if "wsAttributeAssignValues" in assign_body else [] + self.attribute_definition = attribute_def + self.attribute_definition_name = attribute_def_name + + def delete( + self, + act_as_subject: Subject | None = None, + ) -> None: + """Delete this attribute assignment in Grouper. + + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + """ + assign_attribute( + attribute_assign_type=self.attributeAssignType, + assign_operation="remove_attr", + client=self.client, + attribute_assign_id=self.id, + act_as_subject=act_as_subject, + ) diff --git a/grouper_python/objects/group.py b/grouper_python/objects/group.py index b208ae4..08fb635 100644 --- a/grouper_python/objects/group.py +++ b/grouper_python/objects/group.py @@ -7,6 +7,7 @@ from .membership import Membership, HasMember from .client import GrouperClient from .privilege import Privilege + from .attribute import AttributeAssignment from .subject import Subject from dataclasses import dataclass from ..membership import ( @@ -16,7 +17,8 @@ delete_members_from_group, has_members, ) -from ..privilege import assign_privilege, get_privileges +from ..attribute import assign_attribute, get_attribute_assignments +from ..privilege import assign_privileges, get_privileges from ..group import delete_groups @@ -143,11 +145,11 @@ def create_privilege_on_this( :param act_as_subject: Optional subject to act as, defaults to None :type act_as_subject: Subject | None, optional """ - assign_privilege( - target=self.name, + assign_privileges( + target_name=self.name, target_type="group", - privilege_name=privilege_name, - entity_identifier=entity_identifier, + privilege_names=[privilege_name], + entity_identifiers=[entity_identifier], allowed="T", client=self.client, act_as_subject=act_as_subject, @@ -168,17 +170,69 @@ def delete_privilege_on_this( :param act_as_subject: Optional subject to act as, defaults to None :type act_as_subject: Subject | None, optional """ - assign_privilege( - target=self.name, + assign_privileges( + target_name=self.name, target_type="group", - privilege_name=privilege_name, - entity_identifier=entity_identifier, + privilege_names=[privilege_name], + entity_identifiers=[entity_identifier], + allowed="F", + client=self.client, + act_as_subject=act_as_subject, + ) + + def create_privileges_on_this( + self, + entity_identifiers: list[str], + privilege_names: list[str], + act_as_subject: Subject | None = None, + ) -> None: + """Create privileges on this Group. + + :param entity_identifiers: List of identifiers of the entities + to receive the permissions + :type entity_identifiers: list[str] + :param privilege_names: List of names of the privileges to assign + :type privilege_names: list[str] + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + """ + assign_privileges( + target_name=self.name, + target_type="group", + privilege_names=privilege_names, + entity_identifiers=entity_identifiers, + allowed="T", + client=self.client, + act_as_subject=act_as_subject, + ) + + def delete_privileges_on_this( + self, + entity_identifiers: list[str], + privilege_names: list[str], + act_as_subject: Subject | None = None, + ) -> None: + """Delete privileges on this Group. + + :param entity_identifiers: List of identifiers of the entities + to receive the permissions + :type entity_identifiers: list[str] + :param privilege_names: List of names of the privileges to assign + :type privilege_names: list[str] + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + """ + assign_privileges( + target_name=self.name, + target_type="group", + privilege_names=privilege_names, + entity_identifiers=entity_identifiers, allowed="F", client=self.client, act_as_subject=act_as_subject, ) - def get_privilege_on_this( + def get_privileges_on_this( self, subject_id: str | None = None, subject_identifier: str | None = None, @@ -321,6 +375,72 @@ def delete( act_as_subject=act_as_subject, ) + def assign_attribute_on_this( + self, + assign_operation: str, + attribute_def_name_name: str, + value: None | str | int | float = None, + assign_value_operation: str | None = None, + act_as_subject: Subject | None = None, + ) -> list[AttributeAssignment]: + """Assign an attribute on this group. + + :param assign_operation: Assignment operation, one of + "assign_attr", "add_attr", "remove_attr", "replace_attrs" + :type assign_operation: str + :param attribute_def_name_name: Attribute definition name name to assign + :type attribute_def_name_name: str + :param value: Value to assign, also requires assign_value_operation, + defaults to None + :type value: None | str | int | float, optional + :param assign_value_operation: Value assignment operation, one of + "assign_value", "add_value", "remove_value", "replace_values", + requires value to be specified as well, defaults to None + :type assign_value_operation: str | None, optional + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + :return: A list of modified AttributeAssignments + :rtype: list[AttributeAssignment] + """ + return assign_attribute( + attribute_assign_type="group", + assign_operation=assign_operation, + client=self.client, + owner_name=self.name, + attribute_def_name_name=attribute_def_name_name, + value=value, + assign_value_operation=assign_value_operation, + act_as_subject=act_as_subject, + ) + + def get_attribute_assignments_on_this( + self, + attribute_def_name_names: list[str] = [], + attribute_def_names: list[str] = [], + act_as_subject: Subject | None = None, + ) -> list[AttributeAssignment]: + """Get attribute assignments on this group. + + :param attribute_def_name_names: List of names of attribute definition names + to retrieve assignments for, defaults to [] + :type attribute_def_name_names: list[str], optional + :param attribute_def_names: List of names of attribute defitions + to retrieve assignments for, defaults to [] + :type attribute_def_names: list[str], optional + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + :return: A list of AttributeAssignments on this group + :rtype: list[AttributeAssignment] + """ + return get_attribute_assignments( + attribute_assign_type="group", + client=self.client, + attribute_def_name_names=attribute_def_name_names, + attribute_def_names=attribute_def_names, + owner_names=[self.name], + act_as_subject=act_as_subject, + ) + @dataclass class CreateGroup: diff --git a/grouper_python/objects/stem.py b/grouper_python/objects/stem.py index 4004c7c..c3907bd 100644 --- a/grouper_python/objects/stem.py +++ b/grouper_python/objects/stem.py @@ -7,10 +7,12 @@ from .subject import Subject from .group import Group from .privilege import Privilege + from .attribute import AttributeAssignment -from ..privilege import assign_privilege, get_privileges +from ..privilege import assign_privileges, get_privileges from ..stem import create_stems, get_stems_by_parent, delete_stems from ..group import create_groups, get_groups_by_parent +from ..attribute import assign_attribute, get_attribute_assignments from .client import GrouperClient from dataclasses import dataclass from .base import GrouperEntity @@ -63,11 +65,11 @@ def create_privilege_on_this( :param act_as_subject: Optional subject to act as, defaults to None :type act_as_subject: Subject | None, optional """ - assign_privilege( - target=self.name, + assign_privileges( + target_name=self.name, target_type="stem", - privilege_name=privilege_name, - entity_identifier=entity_identifier, + privilege_names=[privilege_name], + entity_identifiers=[entity_identifier], allowed="T", client=self.client, act_as_subject=act_as_subject, @@ -88,17 +90,69 @@ def delete_privilege_on_this( :param act_as_subject: Optional subject to act as, defaults to None :type act_as_subject: Subject | None, optional """ - assign_privilege( - target=self.name, + assign_privileges( + target_name=self.name, target_type="stem", - privilege_name=privilege_name, - entity_identifier=entity_identifier, + privilege_names=[privilege_name], + entity_identifiers=[entity_identifier], + allowed="F", + client=self.client, + act_as_subject=act_as_subject, + ) + + def create_privileges_on_this( + self, + entity_identifiers: list[str], + privilege_names: list[str], + act_as_subject: Subject | None = None, + ) -> None: + """Create privileges on this Stem. + + :param entity_identifiers: List of identifiers of the entities + to receive the permissions + :type entity_identifiers: list[str] + :param privilege_names: List of names of the privileges to assign + :type privilege_names: list[str] + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + """ + assign_privileges( + target_name=self.name, + target_type="stem", + privilege_names=privilege_names, + entity_identifiers=entity_identifiers, + allowed="T", + client=self.client, + act_as_subject=act_as_subject, + ) + + def delete_privileges_on_this( + self, + entity_identifiers: list[str], + privilege_names: list[str], + act_as_subject: Subject | None = None, + ) -> None: + """Delete privileges on this Stem. + + :param entity_identifiers: List of identifiers of the entities + to receive the permissions + :type entity_identifiers: list[str] + :param privilege_names: List of names of the privileges to assign + :type privilege_names: list[str] + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + """ + assign_privileges( + target_name=self.name, + target_type="stem", + privilege_names=privilege_names, + entity_identifiers=entity_identifiers, allowed="F", client=self.client, act_as_subject=act_as_subject, ) - def get_privilege_on_this( + def get_privileges_on_this( self, subject_id: str | None = None, subject_identifier: str | None = None, @@ -258,6 +312,72 @@ def delete( stem_names=[self.name], client=self.client, act_as_subject=act_as_subject ) + def assign_attribute_on_this( + self, + assign_operation: str, + attribute_def_name_name: str, + value: None | str | int | float = None, + assign_value_operation: str | None = None, + act_as_subject: Subject | None = None, + ) -> list[AttributeAssignment]: + """Assign an attribute on this stem. + + :param assign_operation: Assignment operation, one of + "assign_attr", "add_attr", "remove_attr", "replace_attrs" + :type assign_operation: str + :param attribute_def_name_name: Attribute definition name name to assign + :type attribute_def_name_name: str + :param value: Value to assign, also requires assign_value_operation, + defaults to None + :type value: None | str | int | float, optional + :param assign_value_operation: Value assignment operation, one of + "assign_value", "add_value", "remove_value", "replace_values", + requires value to be specified as well, defaults to None + :type assign_value_operation: str | None, optional + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + :return: A list of modified AttributeAssignments + :rtype: list[AttributeAssignment] + """ + return assign_attribute( + attribute_assign_type="stem", + assign_operation=assign_operation, + client=self.client, + owner_name=self.name, + attribute_def_name_name=attribute_def_name_name, + value=value, + assign_value_operation=assign_value_operation, + act_as_subject=act_as_subject, + ) + + def get_attribute_assignments_on_this( + self, + attribute_def_name_names: list[str] = [], + attribute_def_names: list[str] = [], + act_as_subject: Subject | None = None, + ) -> list[AttributeAssignment]: + """Get attribute assignments on this stem. + + :param attribute_def_name_names: List of names of attribute definition names + to retrieve assignments for, defaults to [] + :type attribute_def_name_names: list[str], optional + :param attribute_def_names: List of names of attribute defitions + to retrieve assignments for, defaults to [] + :type attribute_def_names: list[str], optional + :param act_as_subject: Optional subject to act as, defaults to None + :type act_as_subject: Subject | None, optional + :return: A list of AttributeAssignments on this stem + :rtype: list[AttributeAssignment] + """ + return get_attribute_assignments( + attribute_assign_type="stem", + client=self.client, + attribute_def_name_names=attribute_def_name_names, + attribute_def_names=attribute_def_names, + owner_names=[self.name], + act_as_subject=act_as_subject, + ) + @dataclass class CreateStem: diff --git a/grouper_python/privilege.py b/grouper_python/privilege.py index d888988..5a1acd3 100644 --- a/grouper_python/privilege.py +++ b/grouper_python/privilege.py @@ -8,7 +8,7 @@ """ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: # pragma: no cover from .objects.client import GrouperClient @@ -22,26 +22,27 @@ ) -def assign_privilege( - target: str, +def assign_privileges( + target_name: str, target_type: str, - privilege_name: str, - entity_identifier: str, + privilege_names: list[str], + entity_identifiers: list[str], allowed: str, client: GrouperClient, act_as_subject: Subject | None = None, ) -> None: - """Assign (or remove) a permission. + """Assign (or remove) permissions. - :param target: Identifier of the target of the permission - :type target: str + :param target_name: Name of the target of the permission + :type target_name: str :param target_type: Type of target, either "stem" or "group" :type target_type: str - :param privilege_name: Name of the privilege to assign - :type privilege_name: str - :param entity_identifier: Identifier of the entity to receive the permission - :type entity_identifier: str - :param allowed: "T" to add the permission, "F" to remove it + :param privilege_names: List of names of the privileges to assign + :type privilege_names: list[str] + :param entity_identifiers: List of identifiers of the entities + to receive the permissions + :type entity_identifiers: list[str] + :param allowed: "T" to add the permissions, "F" to remove them :type allowed: str :param client: The GrouperClient to use :type client: GrouperClient @@ -50,23 +51,24 @@ def assign_privilege( :raises ValueError: An unknown/unsupported target_type is specified :raises GrouperSuccessException: An otherwise unhandled issue with the result """ - body = { - "WsRestAssignGrouperPrivilegesLiteRequest": { - "allowed": allowed, - "privilegeName": privilege_name, - "subjectIdentifier": entity_identifier, - } + request: dict[str, Any] = { + "allowed": allowed, + "privilegeNames": privilege_names, } + request["wsSubjectLookups"] = [ + {"subjectIdentifier": identifier} for identifier in entity_identifiers + ] if target_type == "stem": - body["WsRestAssignGrouperPrivilegesLiteRequest"]["stemName"] = target - body["WsRestAssignGrouperPrivilegesLiteRequest"]["privilegeType"] = "naming" + request["wsStemLookup"] = {"stemName": target_name} + request["privilegeType"] = "naming" elif target_type == "group": - body["WsRestAssignGrouperPrivilegesLiteRequest"]["groupName"] = target - body["WsRestAssignGrouperPrivilegesLiteRequest"]["privilegeType"] = "access" + request["wsGroupLookup"] = {"groupName": target_name} + request["privilegeType"] = "access" else: raise ValueError( f"Target type must be either 'stem' or 'group', but got '{target_type}'." ) + body = {"WsRestAssignGrouperPrivilegesRequest": request} client._call_grouper( "/grouperPrivileges", body, @@ -166,10 +168,13 @@ def get_privileges( act_as_subject=act_as_subject, ) result = r["WsGetGrouperPrivilegesLiteResult"] - return [ - Privilege(client, priv, result["subjectAttributeNames"]) - for priv in result["privilegeResults"] - ] + if "privilegeResults" in result: + return [ + Privilege(client, priv, result["subjectAttributeNames"]) + for priv in result["privilegeResults"] + ] + else: + return [] except GrouperSuccessException as err: r = err.grouper_result r_code = r["WsGetGrouperPrivilegesLiteResult"]["resultMetadata"]["resultCode"] diff --git a/tests/data.py b/tests/data.py index 2bc2657..b8a4414 100644 --- a/tests/data.py +++ b/tests/data.py @@ -320,27 +320,27 @@ } create_priv_group_request = { - "WsRestAssignGrouperPrivilegesLiteRequest": { + "WsRestAssignGrouperPrivilegesRequest": { "allowed": "T", - "privilegeName": "update", - "subjectIdentifier": "user3333", - "groupName": "test:GROUP1", + "privilegeNames": ["update"], + "wsSubjectLookups": [{"subjectIdentifier": "user3333"}], + "wsGroupLookup": {"groupName": "test:GROUP1"}, "privilegeType": "access", } } delete_priv_group_request = { - "WsRestAssignGrouperPrivilegesLiteRequest": { + "WsRestAssignGrouperPrivilegesRequest": { "allowed": "F", - "privilegeName": "update", - "subjectIdentifier": "user3333", - "groupName": "test:GROUP1", + "privilegeNames": ["update"], + "wsSubjectLookups": [{"subjectIdentifier": "user3333"}], + "wsGroupLookup": {"groupName": "test:GROUP1"}, "privilegeType": "access", } } assign_priv_result_valid = { - "WsAssignGrouperPrivilegesLiteResult": {"resultMetadata": {"success": "T"}} + "WsAssignGrouperPrivilegesResults": {"resultMetadata": {"success": "T"}} } add_member_result_valid = { @@ -469,21 +469,21 @@ } create_priv_stem_request = { - "WsRestAssignGrouperPrivilegesLiteRequest": { + "WsRestAssignGrouperPrivilegesRequest": { "allowed": "T", - "privilegeName": "stemAttrRead", - "subjectIdentifier": "user3333", - "stemName": "test:child", + "privilegeNames": ["stemAttrRead"], + "wsSubjectLookups": [{"subjectIdentifier": "user3333"}], + "wsStemLookup": {"stemName": "test:child"}, "privilegeType": "naming", } } delete_priv_stem_request = { - "WsRestAssignGrouperPrivilegesLiteRequest": { + "WsRestAssignGrouperPrivilegesRequest": { "allowed": "F", - "privilegeName": "stemAttrRead", - "subjectIdentifier": "user3333", - "stemName": "test:child", + "privilegeNames": ["stemAttrRead"], + "wsSubjectLookups": [{"subjectIdentifier": "user3333"}], + "wsStemLookup": {"stemName": "test:child"}, "privilegeType": "naming", } } @@ -626,3 +626,135 @@ "resultMetadata": {"success": "F", "resultCode": "STEM_NOT_FOUND"} } } + +get_priv_for_group_result_none_found = { + "WsGetGrouperPrivilegesLiteResult": {"resultMetadata": {"success": "T"}} +} + +get_attribute_assignment_result_no_assignments = { + "WsGetAttributeAssignmentsResults": { + "resultMetadata": {"success": "T"}, + } +} + +attribute_def = { + "attributeDefType": "attr", + "assignToAttributeDef": "F", + "assignToStemAssignment": "F", + "extension": "prov_to_def", + "assignToMemberAssignment": "F", + "assignToEffectiveMembership": "F", + "uuid": "24b93ca5c9234d1ab8da393afcc24c60", + "assignToImmediateMembershipAssignment": "F", + "assignToEffectiveMembershipAssignment": "F", + "assignToStem": "F", + "assignToGroupAssignment": "F", + "assignToMember": "F", + "multiAssignable": "F", + "valueType": "string", + "name": "etc:attr_def", + "assignToAttributeDefAssignment": "F", + "idIndex": "1000017", + "multiValued": "F", + "assignToGroup": "F", + "assignToImmediateMembership": "F", +} + +attribute_def_name = { + "attributeDefId": "24b93ca5c9234d1ab8da393afcc24c60", + "displayExtension": "attr", + "extension": "attr", + "displayName": "attr", + "name": "etc:attr", + "attributeDefName": "etc:attr_def", + "idIndex": "1000076", + "uuid": "04d2177a412648b7a084f72f503bba4d", +} + +attribute_assignment_group = { + "attributeAssignDelegatable": "FALSE", + "disallowed": "F", + "createdOn": "2023/06/12 09:53:52.253", + "enabled": "T", + "attributeAssignType": "group", + "attributeDefId": "24b93ca5c9234d1ab8da393afcc24c60", + "lastUpdated": "2023/06/12 09:53:52.253", + "attributeAssignActionId": "ae72ff8bf5414933a9bf7fb6fdf04a28", + "ownerGroupName": "test:GROUP1", + "id": "48e69cfa6b2340b5907662a3b5e0d330", + "wsAttributeAssignValues": [ + {"id": "3017a079b5cc48e2a0126f8ba6f303d4", "valueSystem": "value"} + ], + "ownerGroupId": "1ab0482715c74f51bc32822a70bf8f77", + "attributeDefName": "etc:provision_to_def", + "attributeDefNameName": "etc:provision_to", + "attributeAssignActionName": "assign", + "attributeDefNameId": "04d2177a412648b7a084f72f503bba4d", + "attributeAssignActionType": "immediate", +} + +attribute_assignment_stem = { + "attributeAssignDelegatable": "FALSE", + "disallowed": "F", + "createdOn": "2023/06/12 09:53:52.253", + "enabled": "T", + "attributeAssignType": "stem", + "attributeDefId": "24b93ca5c9234d1ab8da393afcc24c60", + "lastUpdated": "2023/06/12 09:53:52.253", + "attributeAssignActionId": "ae72ff8bf5414933a9bf7fb6fdf04a28", + "ownerStemName": "test:child", + "id": "48e69cfa6b2340b5907662a3b5e0d330", + "wsAttributeAssignValues": [ + {"id": "3017a079b5cc48e2a0126f8ba6f303d4", "valueSystem": "value"} + ], + "ownerStemId": "e2c91c056fb746cca551d6887c722215", + "attributeDefName": "etc:provision_to_def", + "attributeDefNameName": "etc:provision_to", + "attributeAssignActionName": "assign", + "attributeDefNameId": "04d2177a412648b7a084f72f503bba4d", + "attributeAssignActionType": "immediate", +} + +get_attribute_assignment_result_group = { + "WsGetAttributeAssignmentsResults": { + "resultMetadata": {"success": "T"}, + "wsAttributeAssigns": [attribute_assignment_group], + "wsAttributeDefs": [attribute_def], + "wsAttributeDefNames": [attribute_def_name], + "wsGroups": [grouper_group_result1], + } +} + +assign_attribute_result_group = { + "WsAssignAttributesResults": { + "resultMetadata": {"success": "T"}, + "wsAttributeDefs": [attribute_def], + "wsAttributeDefNames": [attribute_def_name], + "wsGroups": [grouper_group_result1], + "wsAttributeAssignResults": [ + {"wsAttributeAssigns": [attribute_assignment_group]} + ], + } +} + +get_attribute_assignment_result_stem = { + "WsGetAttributeAssignmentsResults": { + "resultMetadata": {"success": "T"}, + "wsAttributeAssigns": [attribute_assignment_stem], + "wsAttributeDefs": [attribute_def], + "wsAttributeDefNames": [attribute_def_name], + "wsStems": [grouper_stem_1], + } +} + +assign_attribute_result_stem = { + "WsAssignAttributesResults": { + "resultMetadata": {"success": "T"}, + "wsAttributeDefs": [attribute_def], + "wsAttributeDefNames": [attribute_def_name], + "wsStems": [grouper_stem_1], + "wsAttributeAssignResults": [ + {"wsAttributeAssigns": [attribute_assignment_stem]} + ], + } +} diff --git a/tests/test_attribute.py b/tests/test_attribute.py new file mode 100644 index 0000000..1f63f7a --- /dev/null +++ b/tests/test_attribute.py @@ -0,0 +1,99 @@ +# mypy: allow_untyped_defs +from __future__ import annotations +from grouper_python.objects import Group, Stem +from . import data +# import pytest +import respx +from httpx import Response + + +@respx.mock +def test_group_get_attribute_assignments_none(grouper_group: Group): + respx.post(url=data.URI_BASE + "/attributeAssignments").mock( + side_effect=[ + Response(200, json=data.get_attribute_assignment_result_no_assignments) + ] + ) + assgs = grouper_group.get_attribute_assignments_on_this() + + assert len(assgs) == 0 + + +@respx.mock +def test_group_get_attribute_assignments(grouper_group: Group): + respx.post(url=data.URI_BASE + "/attributeAssignments").mock( + side_effect=[ + Response(200, json=data.get_attribute_assignment_result_group) + ] + ) + assgs = grouper_group.get_attribute_assignments_on_this() + + assert len(assgs) == 1 + + +@respx.mock +def test_group_assign_attribute(grouper_group: Group): + respx.post(url=data.URI_BASE + "/attributeAssignments").mock( + side_effect=[ + Response(200, json=data.assign_attribute_result_group) + ] + ) + assgs = grouper_group.assign_attribute_on_this("assign_attr", "etc:attr") + assert len(assgs) == 1 + + +@respx.mock +def test_group_assign_attribute_with_value(grouper_group: Group): + respx.post(url=data.URI_BASE + "/attributeAssignments").mock( + side_effect=[ + Response(200, json=data.assign_attribute_result_group) + ] + ) + assgs = grouper_group.assign_attribute_on_this( + "assign_attr", "etc:attr", "value", "assign_value") + assert len(assgs) == 1 + + +@respx.mock +def test_delete_group_attribute_assignment(grouper_group: Group): + respx.post(url=data.URI_BASE + "/attributeAssignments").mock( + return_value=Response(200, json=data.assign_attribute_result_group) + ) + assgs = grouper_group.assign_attribute_on_this("assign_attr", "etc:attr") + assert len(assgs) == 1 + # If we've gotten this far, we have an assignment that we can then delete + assgs[0].delete() + + +@respx.mock +def test_stem_get_attribute_assignments(grouper_stem: Stem): + respx.post(url=data.URI_BASE + "/attributeAssignments").mock( + side_effect=[ + Response(200, json=data.get_attribute_assignment_result_stem) + ] + ) + assgs = grouper_stem.get_attribute_assignments_on_this() + + assert len(assgs) == 1 + + +@respx.mock +def test_stem_assign_attribute(grouper_stem: Stem): + respx.post(url=data.URI_BASE + "/attributeAssignments").mock( + side_effect=[ + Response(200, json=data.assign_attribute_result_stem) + ] + ) + assgs = grouper_stem.assign_attribute_on_this("assign_attr", "etc:attr") + assert len(assgs) == 1 + + +@respx.mock +def test_delete_stem_attribute_assignment(grouper_stem: Stem): + respx.post(url=data.URI_BASE + "/attributeAssignments").mock( + return_value=Response(200, json=data.assign_attribute_result_stem) + ) + assgs = grouper_stem.assign_attribute_on_this("assign_attr", "etc:attr") + assert len(assgs) == 1 + # If we've gotten this far, we have an assignment that we can then delete + assgs[0].delete() diff --git a/tests/test_group.py b/tests/test_group.py index 88fd087..6ffc3e6 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -133,6 +133,7 @@ def test_create_privilege(grouper_group: Group): ).mock(Response(200, json=data.assign_priv_result_valid)) grouper_group.create_privilege_on_this("user3333", "update") + grouper_group.create_privileges_on_this(["user3333"], ["update"]) @respx.mock @@ -144,6 +145,7 @@ def test_delete_privilege(grouper_group: Group): ).mock(return_value=Response(200, json=data.assign_priv_result_valid)) grouper_group.delete_privilege_on_this("user3333", "update") + grouper_group.delete_privileges_on_this(["user3333"], ["update"]) @respx.mock @@ -154,7 +156,7 @@ def test_get_privilege(grouper_group: Group): json=data.get_priv_for_group_request, ).mock(return_value=Response(200, json=data.get_priv_for_group_result)) - privs = grouper_group.get_privilege_on_this() + privs = grouper_group.get_privileges_on_this() assert len(privs) == 1 diff --git a/tests/test_privilege.py b/tests/test_privilege.py index 1cfb93d..b1205e9 100644 --- a/tests/test_privilege.py +++ b/tests/test_privilege.py @@ -27,7 +27,7 @@ def test_get_privilege_both_group_and_stem(grouper_subject: Subject): def test_get_privilege_both_subject_id_and_identifier(grouper_group: Group): with pytest.raises(ValueError) as excinfo: - grouper_group.get_privilege_on_this( + grouper_group.get_privileges_on_this( subject_id="abcd1234", subject_identifier="user1234" ) @@ -44,11 +44,24 @@ def test_get_privilege_subject_identifier(grouper_group: Group): json=data.get_priv_for_group_with_subject_identifier_request, ).mock(return_value=Response(200, json=data.get_priv_for_group_result)) - privs = grouper_group.get_privilege_on_this(subject_identifier="user3333") + privs = grouper_group.get_privileges_on_this(subject_identifier="user3333") assert len(privs) == 1 +@respx.mock +def test_get_privilege_subject_identifier_none_found(grouper_group: Group): + respx.route( + method="POST", + url=data.URI_BASE + "/grouperPrivileges", + json=data.get_priv_for_group_with_subject_identifier_request, + ).mock(return_value=Response(200, json=data.get_priv_for_group_result_none_found)) + + privs = grouper_group.get_privileges_on_this(subject_identifier="user3333") + + assert len(privs) == 0 + + @respx.mock def test_get_privilege_with_privilege_name(grouper_group: Group): respx.route( @@ -57,7 +70,7 @@ def test_get_privilege_with_privilege_name(grouper_group: Group): json=data.get_priv_for_group_with_privilege_name_request, ).mock(return_value=Response(200, json=data.get_priv_for_group_result)) - privs = grouper_group.get_privilege_on_this(privilege_name="admin") + privs = grouper_group.get_privileges_on_this(privilege_name="admin") assert len(privs) == 1 @@ -94,7 +107,7 @@ def test_get_privilege_subject_identifier_not_found(grouper_group: Group): ) with pytest.raises(GrouperSubjectNotFoundException) as excinfo: - grouper_group.get_privilege_on_this(subject_identifier="user3333") + grouper_group.get_privileges_on_this(subject_identifier="user3333") assert excinfo.value.subject_identifier == "user3333" @@ -106,7 +119,7 @@ def test_get_privilege_subject_id_not_found(grouper_group: Group): ) with pytest.raises(GrouperSubjectNotFoundException) as excinfo: - grouper_group.get_privilege_on_this(subject_id="abcd1234") + grouper_group.get_privileges_on_this(subject_id="abcd1234") assert excinfo.value.subject_identifier == "abcd1234" diff --git a/tests/test_stem.py b/tests/test_stem.py index 539857c..65b0c4c 100644 --- a/tests/test_stem.py +++ b/tests/test_stem.py @@ -14,6 +14,7 @@ def test_create_privilege(grouper_stem: Stem): ).mock(Response(200, json=data.assign_priv_result_valid)) grouper_stem.create_privilege_on_this("user3333", "stemAttrRead") + grouper_stem.create_privileges_on_this(["user3333"], ["stemAttrRead"]) @respx.mock @@ -25,6 +26,7 @@ def test_delete_privilege(grouper_stem: Stem): ).mock(return_value=Response(200, json=data.assign_priv_result_valid)) grouper_stem.delete_privilege_on_this("user3333", "stemAttrRead") + grouper_stem.delete_privileges_on_this(["user3333"], ["stemAttrRead"]) @respx.mock @@ -35,7 +37,7 @@ def test_get_privilege(grouper_stem: Stem): json=data.get_priv_for_stem_request, ).mock(return_value=Response(200, json=data.get_priv_for_stem_result)) - privs = grouper_stem.get_privilege_on_this() + privs = grouper_stem.get_privileges_on_this() assert len(privs) == 1 diff --git a/tests/test_util.py b/tests/test_util.py index ca52aaf..c9b81ff 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -12,7 +12,7 @@ from . import data import pytest from grouper_python.util import call_grouper -from grouper_python.privilege import assign_privilege +from grouper_python.privilege import assign_privileges from grouper_python.membership import has_members, get_members_for_groups from grouper_python.objects.exceptions import ( GrouperAuthException, @@ -174,8 +174,8 @@ def test_grouper_auth_exception(grouper_client: GrouperClient): @respx.mock def test_assign_privilege_unknown_target_type(grouper_client: GrouperClient): with pytest.raises(ValueError) as excinfo: - assign_privilege( - "target:name", "type", "update", "user1234", "T", grouper_client + assign_privileges( + "target:name", "type", ["update"], ["user1234"], "T", grouper_client ) assert ( excinfo.value.args[0]